diff --git a/.Rhistory b/.Rhistory index 626ee6c4..eb574f3a 100644 --- a/.Rhistory +++ b/.Rhistory @@ -1,4 +1,3 @@ -############ learnr, # interactive tutorials in RStudio Tutorial pane swirl, # interactive tutorials in R console # project and file management @@ -510,3 +509,4 @@ bookdown::render_book( output_format = 'bookdown::bs4_book', config_file = "_bookdown.yml") renv::status() +here("data", "linelists", "linelist_raw.xlsx") diff --git a/.github/workflows/create_pr_on_pr.yml b/.github/workflows/create_pr_on_pr.yml index e5459a6d..fb2c9385 100644 --- a/.github/workflows/create_pr_on_pr.yml +++ b/.github/workflows/create_pr_on_pr.yml @@ -14,13 +14,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Set up Git + + - name: Set up Git and Github CLI run: | git config --global user.name 'ntluong95' git config --global user.email 'ph.ntluong95@gmail.com' - - - name: Install GitHub CLI - run: | sudo apt update sudo apt install -y gh @@ -35,97 +33,103 @@ jobs: for lang in "${LANGS[@]}"; do TRANSLATION_BRANCH="handbook_${VERSION_SUFFIX/_en/_$lang}" + git fetch --prune - # Check if the translation branch exists + # Ensure we have all history + git fetch --all + + #! Check if the translation branch exists if git ls-remote --exit-code --heads origin "${TRANSLATION_BRANCH}"; then echo "Branch ${TRANSLATION_BRANCH} exists. Checking out and rebasing with ${EN_BRANCH}" - git fetch --prune git checkout "${TRANSLATION_BRANCH}" - - echo "${EN_BRANCH#refs/heads/}" - echo "${TRANSLATION_BRANCH}" - - git status git pull -s recursive -X theirs --no-rebase --no-edit origin "${EN_BRANCH#refs/heads/}" --allow-unrelated-histories - git push origin "${TRANSLATION_BRANCH}" - else echo "Branch ${TRANSLATION_BRANCH} does not exist. Creating new branch from ${EN_BRANCH}." git checkout -b "${TRANSLATION_BRANCH}" git pull origin "${EN_BRANCH#refs/heads/}" fi - # Force push the changes to the remote repository + #! Force push the changes to the remote repository git push origin "${TRANSLATION_BRANCH}" --force + + #! Get the list of changed .qmd files: COMPARED WITH main branch + changed_files=$(git diff --name-only origin/main "${TRANSLATION_BRANCH}" | grep -E '\.qmd$' | grep -Ev '\.[a-z]{2}\.qmd$') + + if [ -z "$changed_files" ]; then + echo "No .qmd file changes to include in PR for ${TRANSLATION_BRANCH}" + continue + fi + + echo "Changed files: $changed_files" - # Get the date of the latest commit on the english branch + #! Get the date of the latest commit on the english branch latest_commit_date=$(git show -s --format=%ci ${EN_BRANCH}) echo "Commits on the English branch that were made after the latest commit on the translation branch at $latest_commit_date" latest_commit_en_branch=$(git show --format=%H -s ${EN_BRANCH}) latest_commit_info=$(git log ${EN_BRANCH} --since="$latest_commit_date" --format="%H %s" --reverse) commit_messages=$(echo "$latest_commit_info" | cut -d' ' -f2-) - latest_commit_master=$(git show --format=%H -s origin/master) + latest_commit_main=$(git show --format=%H -s origin/master) echo $latest_commit_en_branch - echo $latest_commit_master + echo $latest_commit_main echo $latest_commit_info - # Check if there are new commits - if [ "$latest_commit_en_branch" == "$latest_commit_master" ]; then + #! Check if there are new commits + if [ "$latest_commit_en_branch" == "$latest_commit_main" ]; then echo "No new commits to include in PR for ${TRANSLATION_BRANCH}" continue fi - # Check if a PR already exists for this branch + #! Check if a PR already exists for this branch PR_EXISTS=$(gh pr list --head "${TRANSLATION_BRANCH}" --state open --json number --jq length) if [ "$PR_EXISTS" -eq 0 ]; then echo "Creating new PR for ${TRANSLATION_BRANCH}" PR_URL=$(gh pr create --base deploy-preview --head "$TRANSLATION_BRANCH" --title "Handbook ${VERSION_SUFFIX/_en/} $lang" --body "Automated pull request for $lang handbook version ${VERSION_SUFFIX/_en/}") PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') else - # Get the PR number for the translation branch + #! Get the PR number for the translation branch echo "PR already exists for ${TRANSLATION_BRANCH}" PR_NUMBER=$(gh pr list --head "${TRANSLATION_BRANCH}" --state open --json number --jq ".[0].number") fi echo "Pull Request Number: $PR_NUMBER" + + #! Initialize new PR body + new_pr_body="# Automated pull request for $lang handbook version ${VERSION_SUFFIX/_en/}"$'\n\n' + + #! Mention a user in the PR description + case "$lang" in + "vn") new_pr_body+="@ntluong95, please review changes and check the box when you finish"$'\n' ;; + "fr") new_pr_body+="@oliviabboyd, please review changes and check the box when you finish"$'\n' ;; + "es") new_pr_body+="@amateo250, please review changes and check the box when you finish"$'\n' ;; + "jp") new_pr_body+="@hitomik723, please review changes and check the box when you finish"$'\n' ;; + "tr") new_pr_body+="@ntluong95, please review changes and check the box when you finish"$'\n' ;; + "pt") new_pr_body+="@Luccan97, please review changes and check the box when you finish"$'\n' ;; + "ru") new_pr_body+="@ntluong95, please review changes and check the box when you finish"$'\n' ;; + esac - # Add new commits as checkboxes to the PR description + #! Add new commits as checkboxes to the PR description + IFS=$'\n' # Change the Internal Field Separator to newline for correct iteration over lines checkboxes="" - for commit in $latest_commit_en_branch; do - checkboxes="$checkboxes- [ ] [$commit_messages](https://github.com/${{ github.repository }}/commit/$commit)" + for file in $changed_files; do + #! List only new commits compared with the main branch, tail -n +2 to skip the first line of output from git log, the latest commit will not be added into the message + list_commits=$(git log origin/main..${TRANSLATION_BRANCH} --follow --pretty=format:"%H" -- $file | tail -n +2 | paste -sd, - | sed 's/,/, /g') + for commit in $latest_commit_en_branch; do + checkboxes="$checkboxes- [ ] Chapter [\`$file\`](https://github.com/${{ github.repository }}/pull/$PR_NUMBER/files?file-filters%5B%5D=.qmd&show-viewed-files=true) has changes in the following commit(s): $list_commits. " + checkboxes="$checkboxes"$'\n'"$checkbox" + + done done - - # Mention a user in the PR description - if [ "$lang" = "vn" ]; then - checkboxes="$checkboxes @ntluong95, please check the box when you finish" - elif [ "$lang" = "fr" ]; then - checkboxes="$checkboxes @nsbatra, please check the box when you finish" - elif [ "$lang" = "es" ]; then - checkboxes="$checkboxes @robcrystalornelas, please check the box when you finish" - elif [ "$lang" = "jp" ]; then - checkboxes="$checkboxes @ntluong95, please check the box when you finish" - elif [ "$lang" = "tr" ]; then - checkboxes="$checkboxes @ntluong95, please check the box when you finish" - elif [ "$lang" = "pt" ]; then - checkboxes="$checkboxes @ntluong95, please check the box when you finish" - elif [ "$lang" = "ru" ]; then - checkboxes="$checkboxes @ntluong95, please check the box when you finish" + if [ -n "$checkboxes" ]; then + # Append the checkboxes to the new PR body + new_pr_body="$new_pr_body"$'\n'"$checkboxes" fi - - # Retrieve the current PR description - current_pr_body=$(gh pr view $PR_NUMBER --json body --jq '.body') - - # Append checkboxes to the current PR description - new_pr_body=$(printf "%s\n%s" "$current_pr_body" "$checkboxes") - - # gh api repos/${{ github.repository }}/issues/$PR_NUMBER --method PATCH --field body="$checkboxes" gh api repos/${{ github.repository }}/issues/$PR_NUMBER --method PATCH --field body="$new_pr_body" done env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/_quarto.yml b/_quarto.yml index e9004699..d31f50a7 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -66,7 +66,7 @@ book: - icon: twitter href: "https://twitter.com/appliedepi" - icon: linkedin - href: "https://www.linkedin.com/company/appliedepi/" + href: "https://www.linkedin.com/company/appliedepi" # - icon: github # menu: # - text: Source Code @@ -206,6 +206,7 @@ book: - new_pages/reportfactory.qmd #done - new_pages/flexdashboard.qmd #done - new_pages/shiny_basics.qmd #done + - new_pages/quarto.qmd #done # MISCELLANEOUS - part: "Miscellaneous" diff --git a/data/contact_tracing.rds b/data/contact_tracing.rds new file mode 100644 index 00000000..b9c24ce5 Binary files /dev/null and b/data/contact_tracing.rds differ diff --git a/data/quarto/outbreak_dashboard.qmd b/data/quarto/outbreak_dashboard.qmd new file mode 100644 index 00000000..9a55e537 --- /dev/null +++ b/data/quarto/outbreak_dashboard.qmd @@ -0,0 +1,90 @@ +--- +title: "Outbreak dashboard" +output: + flexdashboard::flex_dashboard: + orientation: columns + vertical_layout: fill +--- + +```{r setup, echo=FALSE} +pacman::p_load(rio, here, tidyverse, flexdashboard, # load packages + flextable, incidence2, epicontacts, DT, janitor) + +linelist <- import(here("data", "case_linelists", "linelist_cleaned.rds")) # import data +``` + +## Column 1 {data-width=500} +### Summary and action items + +This report is for the Incident Command team of the fictional outbreak of Ebola cases. **As of `r format(max(linelist$date_hospitalisation, na.rm=T), "%d %B")` there have been `r nrow(linelist)` cases reported as hospitalized.** + +* Several previously-unaffected areas to the West are now reporting cases +* Internal reviews suggest that better communication is needed between district and provincial level data management teams +* Safe and dignified burial teams are reporting difficulties + +### Review data +#### Cases by hospital +```{r} +linelist %>% + count(hospital) %>% + adorn_totals() %>% + rename("Hospital" = hospital, + "Cases" = n) %>% + knitr::kable() +``` + + +## Column 2 {data-width=500} +### Epidemic curve by age + +```{r} +age_outbreak <- incidence(linelist, "date_onset", "week", groups = "age_cat") +plot(age_outbreak, fill = "age_cat", col_pal = muted, title = "") %>% + plotly::ggplotly() +``` + +### Transmission chain (select cases) +```{r} +# load package +pacman::p_load(epicontacts) + +## generate contacts +contacts <- linelist %>% + transmute( + infector = infector, + case_id = case_id, + location = sample(c("Community", "Nosocomial"), n(), TRUE), + duration = sample.int(10, n(), TRUE) + ) %>% + drop_na(infector) + +## generate epicontacts object +epic <- make_epicontacts( + linelist = linelist, + contacts = contacts, + id = "case_id", + from = "infector", + to = "case_id", + directed = TRUE +) + +## subset epicontacts object +sub <- epic %>% + subset( + node_attribute = list(date_onset = c(as.Date(c("2014-06-30", "2014-06-01")))) + ) %>% + thin("contacts") + +# temporal plot +plot( + sub, + x_axis = "date_onset", + node_color = "outcome", + col_pal = c(Death = "firebrick", Recover = "green"), + arrow_size = 0.5, + node_size = 13, + label = FALSE, + height = 700, + width = 700 +) +``` diff --git a/data/quarto/outbreak_report.html b/data/quarto/outbreak_report.html new file mode 100644 index 00000000..f6da1974 --- /dev/null +++ b/data/quarto/outbreak_report.html @@ -0,0 +1,525 @@ + + + + + + + + + + +Outbreak Situation Report + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+

Outbreak Situation Report

+
+ + + +
+ + +
+
Published
+
+

April 24, 2021

+
+
+ + +
+ + + +
+ + +

This report is for the Incident Command team of the fictional outbreak of Ebola cases. As of 30 April there have been 5888 cases reported as hospitalized.

+
+

Summary table of cases by hospital

+
+
+

hospital

cases

deaths

recovered

Central Hospital

454

193

165

Military Hospital

896

399

309

Missing

1,469

611

514

Other

885

395

290

Port Hospital

1,762

785

579

St. Mark's Maternity Hospital (SMMH)

422

199

126

Total

5,888

2,582

1,983

+
+
+
+
+

Epidemic curve by age

+
+
+
+
+

+
+
+
+
+
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/data/quarto/outbreak_report.qmd b/data/quarto/outbreak_report.qmd new file mode 100644 index 00000000..03d75bf7 --- /dev/null +++ b/data/quarto/outbreak_report.qmd @@ -0,0 +1,40 @@ +--- +title: "Outbreak Situation Report" +date: "4/24/2021" +output: pdf_document +--- + +```{r setup, echo=FALSE, warning =FALSE, message=F} +pacman::p_load(rio, here, tidyverse, janitor, incidence2, flextable) +linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) +set_flextable_defaults(fonts_ignore=TRUE) +``` + +This report is for the Incident Command team of the fictional outbreak of Ebola cases. **As of `r format(max(linelist$date_hospitalisation, na.rm=T), "%d %B")` there have been `r nrow(linelist)` cases reported as hospitalized.** + +## Summary table of cases by hospital + +```{r, echo=F, out.height="75%"} +linelist %>% + filter(!is.na(hospital)) %>% + group_by(hospital) %>% + summarise(cases = n(), + deaths = sum(outcome == "Death", na.rm=T), + recovered = sum(outcome == "Recover", na.rm=T)) %>% + adorn_totals() %>% + qflextable() +``` + +## Epidemic curve by age + +```{r, echo=F, warning=F, message=F, out.height = "50%", out.width="75%"} +# create epicurve +age_outbreak <- incidence( + linelist, + date_index = "date_onset", # date of onset for x-axis + interval = "week", # weekly aggregation of cases + groups = "age_cat") + +# plot +plot(age_outbreak, n_breaks = 3, fill = "age_cat", col_pal = muted, title = "Epidemic curve by age group") +``` diff --git a/data/quarto/outbreak_report_files/figure-html/unnamed-chunk-2-1.png b/data/quarto/outbreak_report_files/figure-html/unnamed-chunk-2-1.png new file mode 100644 index 00000000..0c09380c Binary files /dev/null and b/data/quarto/outbreak_report_files/figure-html/unnamed-chunk-2-1.png differ diff --git a/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap-icons.css b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap-icons.css new file mode 100644 index 00000000..285e4448 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2078 @@ +/*! + * 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?2820a3852bdb9a5832199cc61cec4e65") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.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-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.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/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap-icons.woff b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 00000000..dbeeb055 Binary files /dev/null and b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap-icons.woff differ diff --git a/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap.min.css b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap.min.css new file mode 100644 index 00000000..3beee0f3 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * 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,[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: 17px;--bs-body-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";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--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: #0d6efd;--bs-link-color-rgb: 13, 110, 253;--bs-link-decoration: underline;--bs-link-hover-color: #0a58ca;--bs-link-hover-color-rgb: 10, 88, 202;--bs-code-color: #7d12ba;--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.375rem;--bs-border-radius-sm: 0.25rem;--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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6);border-radius:.375rem}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:#f8f9fa;border-radius:.375rem;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:.25rem}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:.375rem;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:.375rem;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:.25rem}.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:.375rem}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:.375rem}.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:.375rem;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:.25rem}.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:.25em;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:.375rem}.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:.375rem}.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:.25rem}.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:.375rem}.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:.375rem}.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.375rem;--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: #0d6efd;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #0a58ca;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #0a58ca;--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: 49, 132, 253;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.25rem}.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.375rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.375rem - 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:.375rem}.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: #0d6efd;--bs-nav-link-hover-color: #0a58ca;--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.375rem;--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.375rem;--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(253, 254, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fdfeff;--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: #fdfeff;--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.375rem;--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(253, 254, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fdfeff;--bs-navbar-brand-color: #fdfefe;--bs-navbar-brand-hover-color: #fdfeff;--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.375rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.375rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(33, 37, 41, 0.03);--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.375rem;--bs-accordion-inner-border-radius: calc(0.375rem - 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: #0d6efd;--bs-pagination-bg: #ffffff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.375rem;--bs-pagination-hover-color: #0a58ca;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #0a58ca;--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.25rem}.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.375rem;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.375rem;--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.375rem;--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.375rem;--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:.375rem;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.375rem;--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.375rem;--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: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";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;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: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";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;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.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: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}.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-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}.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)}.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)}}@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}}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)}}.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)}: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.375rem);--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}}: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%)}.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}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.375rem;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:rgba(33,37,41,.75)}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}dd code:not(.sourceCode),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}.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 +* +* 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: rgba(33, 37, 41, 0.75);--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.375rem}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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.375rem}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:#f8f9fa;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:rgba(33,37,41,.75);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:rgba(33,37,41,.75);margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#0d6efd}.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:#0d6efd}.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 #0d6efd;color:#0d6efd !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#0d6efd !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:.375rem;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:rgba(33,37,41,.75)}div.callout.callout-style-default>.callout-header{background-color:rgba(33,37,41,.75)}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:.375rem;border-bottom-right-radius:.375rem;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(233,236,239,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}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:.375rem;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:repeat(2, 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/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap.min.js b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap.min.js new file mode 100644 index 00000000..e8f21f70 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * 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=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/data/quarto/outbreak_report_files/libs/clipboard/clipboard.min.js b/data/quarto/outbreak_report_files/libs/clipboard/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * 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 b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/data/quarto/outbreak_report_files/libs/quarto-html/popper.min.js b/data/quarto/outbreak_report_files/libs/quarto-html/popper.min.js new file mode 100644 index 00000000..e3726d72 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.7 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/data/quarto/outbreak_report_files/libs/quarto-html/quarto-syntax-highlighting.css b/data/quarto/outbreak_report_files/libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 00000000..b30ce576 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,205 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; + font-style: inherit; +} + +code span.at { + color: #657422; + font-style: inherit; +} + +code span.ss { + color: #20794D; + font-style: inherit; +} + +code span.an { + color: #5E5E5E; + font-style: inherit; +} + +code span.fu { + color: #4758AB; + font-style: inherit; +} + +code span.st { + color: #20794D; + font-style: inherit; +} + +code span.cf { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +code span.op { + color: #5E5E5E; + font-style: inherit; +} + +code span.er { + color: #AD0000; + font-style: inherit; +} + +code span.bn { + color: #AD0000; + font-style: inherit; +} + +code span.al { + color: #AD0000; + font-style: inherit; +} + +code span.va { + color: #111111; + font-style: inherit; +} + +code span.bu { + font-style: inherit; +} + +code span.ex { + font-style: inherit; +} + +code span.pp { + color: #AD0000; + font-style: inherit; +} + +code span.in { + color: #5E5E5E; + font-style: inherit; +} + +code span.vs { + color: #20794D; + font-style: inherit; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; + font-style: inherit; +} + +code span.ch { + color: #20794D; + font-style: inherit; +} + +code span.dt { + color: #AD0000; + font-style: inherit; +} + +code span.fl { + color: #AD0000; + font-style: inherit; +} + +code span.co { + color: #5E5E5E; + font-style: inherit; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; + font-style: inherit; +} + +code span.sc { + color: #5E5E5E; + font-style: inherit; +} + +code span.dv { + color: #AD0000; + font-style: inherit; +} + +code span.kw { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > *, .margin-caption, .aside" + ); + + let lastBottom = 0; + for (const marginChild of marginChildren) { + if (marginChild.offsetParent !== null) { + // clear the top margin so we recompute it + marginChild.style.marginTop = null; + const top = marginChild.getBoundingClientRect().top + window.scrollY; + if (top < lastBottom) { + const marginChildStyle = window.getComputedStyle(marginChild); + const marginBottom = parseFloat(marginChildStyle["marginBottom"]); + const margin = lastBottom - top + marginBottom; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Recompute the position of margin elements anytime the body size changes + if (window.ResizeObserver) { + const resizeObserver = new window.ResizeObserver( + throttle(() => { + layoutMarginEls(); + if ( + window.document.body.getBoundingClientRect().width < 990 && + isReaderMode() + ) { + quartoToggleReader(); + } + }, 50) + ); + resizeObserver.observe(window.document.body); + } + + const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); + const sidebarEl = window.document.getElementById("quarto-sidebar"); + const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); + const marginSidebarEl = window.document.getElementById( + "quarto-margin-sidebar" + ); + // function to determine whether the element has a previous sibling that is active + const prevSiblingIsActiveLink = (el) => { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id="${anchor}"]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + // This is the no-scroll case where last section should be the active one + sectionIndex = 0; + } else { + // This finds the last section visible on screen that should be made active + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + let elRect; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + child.style.pointerEvents = "none"; + } + + nexttick(() => { + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append( + titleEl.textContent || titleEl.innerText, + toggleIcon + ); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.pointerEvents = null; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + if (!elRect) { + elRect = el.getBoundingClientRect(); + } + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + positionToggle(); + + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + positionToggle(); + }, 50) + ); + + window.addEventListener("quarto-hrChanged", () => { + elRect = undefined; + }); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }); + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + child.style.pointerEvents = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); + for (const tabEl of tabEls) { + const id = tabEl.getAttribute("data-bs-target"); + if (id) { + const columnEl = document.querySelector( + `${id} .column-margin, .tabset-margin-content` + ); + if (columnEl) + tabEl.addEventListener("shown.bs.tab", function (event) { + const el = event.srcElement; + if (el) { + const visibleCls = `${el.id}-margin-content`; + // walk up until we find a parent tabset + let panelTabsetEl = el.parentElement; + while (panelTabsetEl) { + if (panelTabsetEl.classList.contains("panel-tabset")) { + break; + } + panelTabsetEl = panelTabsetEl.parentElement; + } + + if (panelTabsetEl) { + const prevSib = panelTabsetEl.previousElementSibling; + if ( + prevSib && + prevSib.classList.contains("tabset-margin-container") + ) { + const childNodes = prevSib.querySelectorAll( + ".tabset-margin-content" + ); + for (const childEl of childNodes) { + if (childEl.classList.contains(visibleCls)) { + childEl.classList.remove("collapse"); + } else { + childEl.classList.add("collapse"); + } + } + } + } + } + + layoutMarginEls(); + }); + } + } + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const boundRect = el.getBoundingClientRect(); + const top = + boundRect.top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + let hasObserved = false; + const visibleItemObserver = (els) => { + let visibleElements = [...els]; + const intersectionObserver = new IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (visibleElements.indexOf(entry.target) === -1) { + visibleElements.push(entry.target); + } + } else { + visibleElements = visibleElements.filter((visibleEntry) => { + return visibleEntry !== entry; + }); + } + }); + + if (!hasObserved) { + hideOverlappedSidebars(); + } + hasObserved = true; + }, + {} + ); + els.forEach((el) => { + intersectionObserver.observe(el); + }); + + return { + getVisibleEntries: () => { + return visibleElements; + }, + }; + }; + + const rightElementObserver = visibleItemObserver(rightSideConflictEls); + const leftElementObserver = visibleItemObserver(leftSideConflictEls); + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); + sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility( + toRegions(leftElementObserver.getVisibleEntries()) + ); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); + const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (tocOpenDepth === -1 && depth > 1) { + // toc-expand: false + el.classList.add("collapse"); + } else if ( + depth <= tocOpenDepth || + hasActiveChild || + prevSiblingIsActiveLink(el) + ) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/data/quarto/outbreak_report_files/libs/quarto-html/tippy.css b/data/quarto/outbreak_report_files/libs/quarto-html/tippy.css new file mode 100644 index 00000000..e6ae635c --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/data/quarto/outbreak_report_files/libs/quarto-html/tippy.umd.min.js b/data/quarto/outbreak_report_files/libs/quarto-html/tippy.umd.min.js new file mode 100644 index 00000000..ca292be3 --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/data/quarto/outbreak_report_files/libs/tabwid-1.1.3/tabwid.css b/data/quarto/outbreak_report_files/libs/tabwid-1.1.3/tabwid.css new file mode 100644 index 00000000..5e31a86c --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/tabwid-1.1.3/tabwid.css @@ -0,0 +1,42 @@ +.tabwid { + font-size: initial; + padding-bottom: 1em; +} + +.tabwid table{ + border-spacing:0px !important; + border-collapse:collapse; + line-height:1; + margin-left:auto; + margin-right:auto; + border-width: 0; + border-color: transparent; + caption-side: top; +} +.tabwid-caption-bottom table{ + caption-side: bottom; +} +.tabwid_left table{ + margin-left:0; +} +.tabwid_right table{ + margin-right:0; +} +.tabwid td, .tabwid th { + padding: 0; +} +.tabwid a { + text-decoration: none; +} +.tabwid thead { + background-color: transparent; +} +.tabwid tfoot { + background-color: transparent; +} +.tabwid table tr { +background-color: transparent; +} +.katex-display { + margin: 0 0 !important; +} diff --git a/data/quarto/outbreak_report_files/libs/tabwid-1.1.3/tabwid.js b/data/quarto/outbreak_report_files/libs/tabwid-1.1.3/tabwid.js new file mode 100644 index 00000000..47070f7b --- /dev/null +++ b/data/quarto/outbreak_report_files/libs/tabwid-1.1.3/tabwid.js @@ -0,0 +1,20 @@ +document.addEventListener("DOMContentLoaded", function(event) { + var els = document.querySelectorAll(".tabwid"); + var tabwid_link = document.querySelector('link[href*="tabwid.css"]') + if (tabwid_link === null) { + const tabwid_styles = document.evaluate("//style[contains(., 'tabwid')]", document, null, XPathResult.ANY_TYPE, null ); + tabwid_link = tabwid_styles.iterateNext(); + } + + Array.prototype.forEach.call(els, function(template) { + const dest = document.createElement("div"); + template.parentNode.insertBefore(dest, template.nextSibling) + dest.setAttribute("class", "flextable-shadow-host"); + const fantome = dest.attachShadow({mode: 'open'}); + fantome.appendChild(template); + if (tabwid_link !== null) { + fantome.appendChild(tabwid_link.cloneNode(true)); + } + }); +}); + diff --git a/data/rmarkdown/outbreak_report.Rmd b/data/rmarkdown/outbreak_report.Rmd index 4ce0c4cd..bdcdf40b 100644 --- a/data/rmarkdown/outbreak_report.Rmd +++ b/data/rmarkdown/outbreak_report.Rmd @@ -31,10 +31,10 @@ linelist %>% # create epicurve age_outbreak <- incidence( linelist, - date_index = date_onset, # date of onset for x-axis + date_index = "date_onset", # date of onset for x-axis interval = "week", # weekly aggregation of cases - groups = age_cat) + groups = "age_cat") # plot -plot(age_outbreak, n_breaks = 3, fill = age_cat, col_pal = muted, title = "Epidemic curve by age group") +plot(age_outbreak, n_breaks = 3, fill = "age_cat", col_pal = muted, title = "Epidemic curve by age group") ``` diff --git a/data/surveys/tab_survey_output.rds b/data/surveys/tab_survey_output.rds new file mode 100644 index 00000000..0ef8e1b5 Binary files /dev/null and b/data/surveys/tab_survey_output.rds differ diff --git a/html_outputs/es/new_pages/data_used.es.html b/html_outputs/es/new_pages/data_used.es.html index d8127699..e8a3c55b 100644 --- a/html_outputs/es/new_pages/data_used.es.html +++ b/html_outputs/es/new_pages/data_used.es.html @@ -707,7 +707,7 @@

Utilizando el enlace de descarga

-

Para acceder rápidamente, clica con el botón derecho en este enlace y selecciona “Guardar enlace como”.

+

Para acceder rápidamente, clica con el botón derecho en este enlace y selecciona “Guardar enlace como”.

Si es un Mac, utiliza Cmd+clic. Si es un móvil, mantén clicado el enlace y selecciona “Guardar enlace”. El manual se descargará en el dispositivo. Si aparece una pantalla con código HTML sin procesar, asegúrate de haber seguido las instrucciones anteriores o prueba la opción 2.

diff --git a/html_outputs/fr/new_pages/data_used.fr.html b/html_outputs/fr/new_pages/data_used.fr.html index f60a99f1..d889b9e2 100644 --- a/html_outputs/fr/new_pages/data_used.fr.html +++ b/html_outputs/fr/new_pages/data_used.fr.html @@ -710,7 +710,7 @@

Il y a deux façons de télécharger le manuel :

Utiliser le lien de téléchargement

-

Pour un accès rapide, cliquez à droite ce lien et sélectionnez “Enregistrer le lien sous”.

+

Pour un accès rapide, cliquez à droite ce lien et sélectionnez “Enregistrer le lien sous”.

Si vous êtes sur un Mac, utilisez Cmd + clic. Si vous êtes sur un téléphone portable, appuyez sur le lien et maintenez-le enfoncé, puis sélectionnez “Enregistrer le lien”. Le manuel sera téléchargé sur votre appareil. Si un écran contenant un code HTML brut apparaît, assurez-vous d’avoir suivi les instructions ci-dessus ou essayez l’option 2.

diff --git a/html_outputs/images/markdown/11_childdocument_call.PNG b/html_outputs/images/markdown/11_childdocument_call.PNG new file mode 100644 index 00000000..83b74e3f Binary files /dev/null and b/html_outputs/images/markdown/11_childdocument_call.PNG differ diff --git a/html_outputs/images/markdown/12_notsundayanalysis.PNG b/html_outputs/images/markdown/12_notsundayanalysis.PNG new file mode 100644 index 00000000..d7484197 Binary files /dev/null and b/html_outputs/images/markdown/12_notsundayanalysis.PNG differ diff --git a/html_outputs/images/markdown/12_sundayanalysis.PNG b/html_outputs/images/markdown/12_sundayanalysis.PNG new file mode 100644 index 00000000..e16c98b3 Binary files /dev/null and b/html_outputs/images/markdown/12_sundayanalysis.PNG differ diff --git a/html_outputs/images/markdown/1_gettingstartedB.png b/html_outputs/images/markdown/1_gettingstartedB.png index 98117732..32e8b0da 100644 Binary files a/html_outputs/images/markdown/1_gettingstartedB.png and b/html_outputs/images/markdown/1_gettingstartedB.png differ diff --git a/html_outputs/images/quarto/0_quartoimg.PNG b/html_outputs/images/quarto/0_quartoimg.PNG new file mode 100644 index 00000000..23f6305b Binary files /dev/null and b/html_outputs/images/quarto/0_quartoimg.PNG differ diff --git a/html_outputs/images/quarto/1_createquarto.PNG b/html_outputs/images/quarto/1_createquarto.PNG new file mode 100644 index 00000000..cadc11d2 Binary files /dev/null and b/html_outputs/images/quarto/1_createquarto.PNG differ diff --git a/html_outputs/images/quarto/2_namingquarto.PNG b/html_outputs/images/quarto/2_namingquarto.PNG new file mode 100644 index 00000000..b649d87d Binary files /dev/null and b/html_outputs/images/quarto/2_namingquarto.PNG differ diff --git a/html_outputs/images/quarto/3_quartovisual.PNG b/html_outputs/images/quarto/3_quartovisual.PNG new file mode 100644 index 00000000..dab728d2 Binary files /dev/null and b/html_outputs/images/quarto/3_quartovisual.PNG differ diff --git a/html_outputs/images/quarto/4_quarto_script.png b/html_outputs/images/quarto/4_quarto_script.png new file mode 100644 index 00000000..cb129acc Binary files /dev/null and b/html_outputs/images/quarto/4_quarto_script.png differ diff --git a/html_outputs/images/quarto/5_quarto_report.png b/html_outputs/images/quarto/5_quarto_report.png new file mode 100644 index 00000000..ee9b51d9 Binary files /dev/null and b/html_outputs/images/quarto/5_quarto_report.png differ diff --git a/html_outputs/images/quarto/rstudio-qmd-how-it-works.png b/html_outputs/images/quarto/rstudio-qmd-how-it-works.png new file mode 100644 index 00000000..1c2900c7 Binary files /dev/null and b/html_outputs/images/quarto/rstudio-qmd-how-it-works.png differ diff --git a/html_outputs/index.html b/html_outputs/index.html index 424caa75..a6ef4508 100644 --- a/html_outputs/index.html +++ b/html_outputs/index.html @@ -1,10 +1,12 @@ - - - + + - + + + + The Epidemiologist R Handbook - + + + + + + + + + - - + + + + + + + - + + - + + + + + + + + + + + - - -
+ + + +
+ +
+ +
-
+ + + +
-
+
+ +
+ +
-
+
+ +
+

The Epidemiologist R Handbook

@@ -631,7 +725,7 @@

The Epidemiologist R Handbook

Last updated
-

Jun 19, 2024

+

Oct 30, 2024

@@ -640,18 +734,33 @@

The Epidemiologist R Handbook

-

Welcome

+
+ + +
+

Welcome

-

+
+

-

R for applied epidemiology and public health

+ + + + + + + + + +
+

R for applied epidemiology and public health

Usage: This handbook has been used over 3 million times by 850,000 people around the world.

Objective: Serve as a quick R code reference manual (online and offline) with task-centered examples that address common epidemiological problems.

Are you just starting with R? Try our free interactive tutorials or synchronous, virtual intro course used by US CDC, WHO, and 400+ other health agencies and Field Epi Training Programs worldwide.

@@ -663,20 +772,21 @@

The Epidemiologist R Handbook

-


Written by epidemiologists, for epidemiologists

+


Written by epidemiologists, for epidemiologists

-

+
+

-

 

@@ -684,34 +794,36 @@

The Epidemiologist R Handbook

-

We offer live R training from instructors with decades of applied epidemiology experience - www.appliedepi.org/live.

+

We offer live R training from instructors with decades of applied epidemiology experience - www.appliedepi.org/live.

-

+

-

How to use this handbook

+
+
+

How to use this handbook

  • Browse the pages in the Table of Contents, or use the search box
  • Click the “copy” icons to copy code
  • -
  • You can follow-along with the example data -
  • +
  • You can follow-along with the example data

Offline version

See instructions in the Download handbook and data page.

-

Acknowledgements

+
+
+

Acknowledgements

This handbook is produced by an independent collaboration of epidemiologists from around the world drawing upon experience with organizations including local, state, provincial, and national health agencies, the World Health Organization (WHO), Doctors without Borders (MSF), hospital systems, and academic institutions.

This handbook is not an approved product of any specific organization. Although we strive for accuracy, we provide no guarantee of the content in this book.

-

Contributors

+
+

Contributors

Editor: Neale Batra

Authors: Neale Batra, Alex Spina, Paula Blomquist, Finlay Campbell, Henry Laurenson-Schafer, Isaac Florence, Natalie Fischer, Aminata Ndiaye, Liza Coyer, Jonathan Polonsky, Yurie Izawa, Chris Bailey, Daniel Molling, Isha Berry, Emma Buajitti, Mathilde Mousset, Sara Hollis, Wen Lin

Reviewers and supporters: Pat Keating, Amrish Baidjoe, Annick Lenglet, Margot Charette, Danielly Xavier, Marie-Amélie Degail Chabrat, Esther Kukielka, Michelle Sloan, Aybüke Koyuncu, Rachel Burke, Kate Kelsey, Berhe Etsay, John Rossow, Mackenzie Zendt, James Wright, Laura Haskins, Flavio Finger, Tim Taylor, Jae Hyoung Tim Lee, Brianna Bradley, Wayne Enanoria, Manual Albela Miranda, Molly Mantus, Pattama Ulrich, Joseph Timothy, Adam Vaughan, Olivia Varsaneux, Lionel Monteiro, Joao Muianga

@@ -720,29 +832,51 @@

The Epidemiologist R Handbook

-

Funding and support

+
+
+

Funding and support

This book was primarily a volunteer effort that took thousands of hours to create.

The handbook received some supportive funding via a COVID-19 emergency capacity-building grant from TEPHINET, the global network of Field Epidemiology Training Programs (FETPs).

Administrative support was provided by the EPIET Alumni Network (EAN), with special thanks to Annika Wendland. EPIET is the European Programme for Intervention Epidemiology Training.

Special thanks to Médecins Sans Frontières (MSF) Operational Centre Amsterdam (OCA) for their support during the development of this handbook.

-

This publication was supported by Cooperative Agreement number NU2GGH001873, funded by the Centers for Disease Control and Prevention through TEPHINET, a program of The Task Force for Global Health. Its contents are solely the responsibility of the authors and do not necessarily represent the official views of the Centers for Disease Control and Prevention, the Department of Health and Human Services, The Task Force for Global Health, Inc. or TEPHINET.

-

Inspiration

+

This publication was supported by Cooperative Agreement number NU2GGH001873, funded by the Centers for Disease Control and Prevention through TEPHINET, a program of The Task Force for Global Health. Its contents are solely the responsibility of the authors and do not necessarily represent the official views of the Centers for Disease Control and Prevention, the Department of Health and Human Services, The Task Force for Global Health, Inc. or TEPHINET.

+
+
+

Inspiration

The multitude of tutorials and vignettes that provided knowledge for development of handbook content are credited within their respective pages.

-

More generally, the following sources provided inspiration for this handbook:
The “R4Epis” project (a collaboration between MSF and RECON)
R Epidemics Consortium (RECON)
R for Data Science book (R4DS)
bookdown: Authoring Books and Technical Documents with R Markdown
Netlify hosts this website

+

More generally, the following sources provided inspiration for this handbook:
+The “R4Epis” project (a collaboration between MSF and RECON)
+R Epidemics Consortium (RECON)
+R for Data Science book (R4DS)
+bookdown: Authoring Books and Technical Documents with R Markdown
+Netlify hosts this website

-

Terms of Use and Contribution

-

License

+
+
+
+

Terms of Use and Contribution

+
+

License

Creative Commons License Applied Epi Incorporated, 2021
This work is licensed by Applied Epi Incorporated under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Academic courses and epidemiologist training programs are welcome to contact us about use or adaptation of this material (email contact@appliedepi.org).

-

Citation

-

Batra, Neale, et al. The Epidemiologist R Handbook. 2021. DOI

-

Contribution

+
+
+

Citation

+

Batra, Neale, et al. The Epidemiologist R Handbook. 2021. DOI

+
+
+

Contribution

If you would like to make a content contribution, please contact with us first via Github issues or by email. We are implementing a schedule for updates and are creating a contributor guide.

Please note that the epiRhandbook project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

-
diff --git a/html_outputs/new_pages/age_pyramid.html b/html_outputs/new_pages/age_pyramid.html index 2631342b..f4546dc6 100644 --- a/html_outputs/new_pages/age_pyramid.html +++ b/html_outputs/new_pages/age_pyramid.html @@ -2,12 +2,12 @@ - + -The Epidemiologist R Handbook - 33  Demographic pyramids and Likert-scales +33  Demographic pyramids and Likert-scales – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + +
+
+ +
+ +
+ + +
+ + + +
+ +
+
+

44  Quarto

+
+ + + +
+ + + + +
+ + + +
+ + +
+
+
+
+

+
+
+
+
+

Quarto is an alternative approach to R Markdown for creating automated, reproducible reports, presentations and interactive dashboards. Quarto was developed by the team behind RStudio, to take the functionality of R Markdown, with other approaches, and combine them into a single consistent system.

+

As the overall philosophy and syntax of Quarto is nearly identical to that of R Markdown, this chapter will not focus on these, and instead refer you to the R Markdown chapter for instructions on syntax and application. We will approach the following topics:

+
    +
  1. Why Quarto over R Markdown?
  2. +
  3. How to get started in Quarto.
  4. +
  5. Going beyond reports to dashboards and interactive documents.
  6. +
+
+

44.1 Why Quarto over R Markdown?

+

Quarto provides the flexibility and automation that R Markdown pioneered, with a more powerful underlying flexibility that streamlines a lot of the sharing and publishing processes of your work! While R Markdown is tied to R, Quarto documents allow you to use a number of different programming languages such as R, Python, Javascript and Julia. While R Markdown can do this, the approach is less straightforward. By streamlining the inclusion of multiple programming languages, it makes collaborating across different people and groups, easier if multiple approaches are used.

+

Even if you are only working in R, Quarto has several advantages. Rather than relying on individual packages to create different outputs, Quarto handles this all internally. To produce a website, PowerPoint slides, or a html report, rather than using external packages, Quarto has this functionality inbuilt. This means you have fewer packages to update, and if one of the packages is no longer maintained, you don’t need to change your approach to maintain the same output.

+

Quarto also has the advantage of providing a way to render multiple files at the same time, and combine them when wanted. The Epi R Handbook is written as several individual Quarto files, and then combined to make this website!

+

If you are thinking of moving over from R Markdown to Quarto, don’t worry, you won’t have to re-write every R Markdown document to move over to Quarto! Quarto documents follow the same syntax and commands as Rmarkdown. Previous scripts in R Markdown can generally be moved over to Quarto without changing any of your code!

+

Below is the pipeline through which Quarto documents are created, as you can see it is very similar to the R Markdown pipeline, other than the initial file type as changed.

+
+
+
+
+

+
+
+
+
+

Source

+
+

Limitations of Quarto

+

There is one substantial limitation of Quarto, compared to R Markdown, when it is used to generate multiple different reports using the same dataset.

+

In R Markdown, you can load in a dataset, run your analyses, and then generate multiple different reports from this dataset in your R environment. This means that you do not need to individually import the dataset for each report.

+

However, because of the way that Quarto works, every single report needs to be self contained. That is to say that you would need to import the dataset every time you want a new report.

+

This is not a problem if you are only running a single report, or a handful. But if you have a large dataset, and need to make a large number of reports, this can quickly become a real problem.

+

Imagine a scenario where you are conducting a national outbreak response. You have a dataset which takes 5 minutes to import, and then you need to generate a report for each of the 50 provinces in the country.

+
    +
  • For R Markdown, you would only need to read in the data once, and then run your reports. This would take a minimum of 5 minutes (5 minutes to read in the data + time to run the script for the reports).

  • +
  • For Quarto, you would need to read in the dataset each time. This would mean you would need at least 250 minutes (5 minutes for each of the 50 provinces).

  • +
+

This quickly increasing time burden means that if you are generating multiple reports based on a large dataset, you may want to use R Markdown over Quarto!

+

For further explanation, please see these forum posts:

+

Quarto can’t find R-objects

+

How to quarto_render into console environment

+
+
+
+

44.2 Getting started

+

Creating a Quarto document does not require us to download an additional package, unlike R Markdown which requires the rmarkdown package. Additionally, you do not need to install LaTex, as you do with R Markdown, as Quarto contains built in functionality.

+

Functionally, Quarto works the same way as R Markdown. You create your Quarto script (instead of your R Markdown file), write your code, and knit the document.

+

First, just like when you create an R Markdown document in Rstudio you start with File, then New file, then R Markdown.

+
+
+
+
+

+
+
+
+
+

You will then get a number of different options to choose. Here we will select “HTML” to create an html document. All these details can be changed later in the document, so do not worry if you change your mind later.

+
+
+
+
+

+
+
+
+
+

This will create your new Quarto script. Note: While the R Markdown scripts ended with .Rmd, Quarto scripts end with .qmd

+

While R Markdown scripts set the working directory to wherever the file is located, Quarto documents retain the original working directory. This is especially useful when you are working with an R Project.

+

Like for R Markdown, Quarto used in RStudio allows you to see what the rendered document will look like after it has been knit. To switch between the “Visual” and “Source” mode, click the “Visual” button in the top left side of the script.

+
+
+
+
+

+
+
+
+
+

You are now ready to code your Quarto script! The syntax and approach is the same as creating an R Markdown document, so see the chapter on Reports with R Markdown for guidance and inspiration.

+

Here is an example of what a Quarto script for analysing our linelist data set might look like.

+
+
+
+
+

+
+
+
+
+

And here is what the output looks like.

+
+
+
+
+

+
+
+
+
+
+
+

44.3 Moving beyond simple reports

+

You may want to move beyond creating simple, static, reports to interactive dashboards and outputs, like you can in R Markdown. Luckily you can do all of this in Quarto, using inbuilt functionality, and other packages like Shiny! For an example of how far you can take your Quarto scripts, see this Quarto Gallery.

+
+
+

44.4 Resources

+

I’m an R user: Quarto or R Markdown?

+

FAQ for R Markdown Users

+

Quarto tutorial

+

Quarto Gallery

+

Create & Publish a Quarto Blog on QUarto Pub in 100 seconds

+ + +
+ +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.css b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.css new file mode 100644 index 00000000..285e4448 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2078 @@ +/*! + * 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?2820a3852bdb9a5832199cc61cec4e65") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.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-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.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/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.woff b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 00000000..dbeeb055 Binary files /dev/null and b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.woff differ diff --git a/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap.min.css b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap.min.css new file mode 100644 index 00000000..3beee0f3 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * 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,[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: 17px;--bs-body-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";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--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: #0d6efd;--bs-link-color-rgb: 13, 110, 253;--bs-link-decoration: underline;--bs-link-hover-color: #0a58ca;--bs-link-hover-color-rgb: 10, 88, 202;--bs-code-color: #7d12ba;--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.375rem;--bs-border-radius-sm: 0.25rem;--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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6);border-radius:.375rem}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:#f8f9fa;border-radius:.375rem;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:.25rem}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:.375rem;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:.375rem;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:.25rem}.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:.375rem}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:.375rem}.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:.375rem;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:.25rem}.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:.25em;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:.375rem}.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:.375rem}.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:.25rem}.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:.375rem}.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:.375rem}.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.375rem;--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: #0d6efd;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #0a58ca;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #0a58ca;--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: 49, 132, 253;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.25rem}.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.375rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.375rem - 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:.375rem}.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: #0d6efd;--bs-nav-link-hover-color: #0a58ca;--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.375rem;--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.375rem;--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(253, 254, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fdfeff;--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: #fdfeff;--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.375rem;--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(253, 254, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fdfeff;--bs-navbar-brand-color: #fdfefe;--bs-navbar-brand-hover-color: #fdfeff;--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.375rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.375rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(33, 37, 41, 0.03);--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.375rem;--bs-accordion-inner-border-radius: calc(0.375rem - 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: #0d6efd;--bs-pagination-bg: #ffffff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.375rem;--bs-pagination-hover-color: #0a58ca;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #0a58ca;--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.25rem}.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.375rem;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.375rem;--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.375rem;--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.375rem;--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:.375rem;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.375rem;--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.375rem;--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: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";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;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: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";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;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.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: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}.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-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}.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)}.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)}}@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}}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)}}.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)}: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.375rem);--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}}: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%)}.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}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.375rem;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:rgba(33,37,41,.75)}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}dd code:not(.sourceCode),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}.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 +* +* 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: rgba(33, 37, 41, 0.75);--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.375rem}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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.375rem}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:#f8f9fa;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:rgba(33,37,41,.75);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:rgba(33,37,41,.75);margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#0d6efd}.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:#0d6efd}.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 #0d6efd;color:#0d6efd !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#0d6efd !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:.375rem;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:rgba(33,37,41,.75)}div.callout.callout-style-default>.callout-header{background-color:rgba(33,37,41,.75)}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:.375rem;border-bottom-right-radius:.375rem;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(233,236,239,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}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:.375rem;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:repeat(2, 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/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap.min.js b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap.min.js new file mode 100644 index 00000000..e8f21f70 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * 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=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/html_outputs/new_pages/quarto_files/libs/clipboard/clipboard.min.js b/html_outputs/new_pages/quarto_files/libs/clipboard/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * 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 b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/html_outputs/new_pages/quarto_files/libs/quarto-html/popper.min.js b/html_outputs/new_pages/quarto_files/libs/quarto-html/popper.min.js new file mode 100644 index 00000000..e3726d72 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.7 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/html_outputs/new_pages/quarto_files/libs/quarto-html/quarto-syntax-highlighting.css b/html_outputs/new_pages/quarto_files/libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 00000000..b30ce576 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,205 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; + font-style: inherit; +} + +code span.at { + color: #657422; + font-style: inherit; +} + +code span.ss { + color: #20794D; + font-style: inherit; +} + +code span.an { + color: #5E5E5E; + font-style: inherit; +} + +code span.fu { + color: #4758AB; + font-style: inherit; +} + +code span.st { + color: #20794D; + font-style: inherit; +} + +code span.cf { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +code span.op { + color: #5E5E5E; + font-style: inherit; +} + +code span.er { + color: #AD0000; + font-style: inherit; +} + +code span.bn { + color: #AD0000; + font-style: inherit; +} + +code span.al { + color: #AD0000; + font-style: inherit; +} + +code span.va { + color: #111111; + font-style: inherit; +} + +code span.bu { + font-style: inherit; +} + +code span.ex { + font-style: inherit; +} + +code span.pp { + color: #AD0000; + font-style: inherit; +} + +code span.in { + color: #5E5E5E; + font-style: inherit; +} + +code span.vs { + color: #20794D; + font-style: inherit; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; + font-style: inherit; +} + +code span.ch { + color: #20794D; + font-style: inherit; +} + +code span.dt { + color: #AD0000; + font-style: inherit; +} + +code span.fl { + color: #AD0000; + font-style: inherit; +} + +code span.co { + color: #5E5E5E; + font-style: inherit; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; + font-style: inherit; +} + +code span.sc { + color: #5E5E5E; + font-style: inherit; +} + +code span.dv { + color: #AD0000; + font-style: inherit; +} + +code span.kw { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > *, .margin-caption, .aside" + ); + + let lastBottom = 0; + for (const marginChild of marginChildren) { + if (marginChild.offsetParent !== null) { + // clear the top margin so we recompute it + marginChild.style.marginTop = null; + const top = marginChild.getBoundingClientRect().top + window.scrollY; + if (top < lastBottom) { + const marginChildStyle = window.getComputedStyle(marginChild); + const marginBottom = parseFloat(marginChildStyle["marginBottom"]); + const margin = lastBottom - top + marginBottom; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Recompute the position of margin elements anytime the body size changes + if (window.ResizeObserver) { + const resizeObserver = new window.ResizeObserver( + throttle(() => { + layoutMarginEls(); + if ( + window.document.body.getBoundingClientRect().width < 990 && + isReaderMode() + ) { + quartoToggleReader(); + } + }, 50) + ); + resizeObserver.observe(window.document.body); + } + + const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); + const sidebarEl = window.document.getElementById("quarto-sidebar"); + const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); + const marginSidebarEl = window.document.getElementById( + "quarto-margin-sidebar" + ); + // function to determine whether the element has a previous sibling that is active + const prevSiblingIsActiveLink = (el) => { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id="${anchor}"]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + // This is the no-scroll case where last section should be the active one + sectionIndex = 0; + } else { + // This finds the last section visible on screen that should be made active + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + let elRect; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + child.style.pointerEvents = "none"; + } + + nexttick(() => { + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append( + titleEl.textContent || titleEl.innerText, + toggleIcon + ); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.pointerEvents = null; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + if (!elRect) { + elRect = el.getBoundingClientRect(); + } + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + positionToggle(); + + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + positionToggle(); + }, 50) + ); + + window.addEventListener("quarto-hrChanged", () => { + elRect = undefined; + }); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }); + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + child.style.pointerEvents = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); + for (const tabEl of tabEls) { + const id = tabEl.getAttribute("data-bs-target"); + if (id) { + const columnEl = document.querySelector( + `${id} .column-margin, .tabset-margin-content` + ); + if (columnEl) + tabEl.addEventListener("shown.bs.tab", function (event) { + const el = event.srcElement; + if (el) { + const visibleCls = `${el.id}-margin-content`; + // walk up until we find a parent tabset + let panelTabsetEl = el.parentElement; + while (panelTabsetEl) { + if (panelTabsetEl.classList.contains("panel-tabset")) { + break; + } + panelTabsetEl = panelTabsetEl.parentElement; + } + + if (panelTabsetEl) { + const prevSib = panelTabsetEl.previousElementSibling; + if ( + prevSib && + prevSib.classList.contains("tabset-margin-container") + ) { + const childNodes = prevSib.querySelectorAll( + ".tabset-margin-content" + ); + for (const childEl of childNodes) { + if (childEl.classList.contains(visibleCls)) { + childEl.classList.remove("collapse"); + } else { + childEl.classList.add("collapse"); + } + } + } + } + } + + layoutMarginEls(); + }); + } + } + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const boundRect = el.getBoundingClientRect(); + const top = + boundRect.top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + let hasObserved = false; + const visibleItemObserver = (els) => { + let visibleElements = [...els]; + const intersectionObserver = new IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (visibleElements.indexOf(entry.target) === -1) { + visibleElements.push(entry.target); + } + } else { + visibleElements = visibleElements.filter((visibleEntry) => { + return visibleEntry !== entry; + }); + } + }); + + if (!hasObserved) { + hideOverlappedSidebars(); + } + hasObserved = true; + }, + {} + ); + els.forEach((el) => { + intersectionObserver.observe(el); + }); + + return { + getVisibleEntries: () => { + return visibleElements; + }, + }; + }; + + const rightElementObserver = visibleItemObserver(rightSideConflictEls); + const leftElementObserver = visibleItemObserver(leftSideConflictEls); + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); + sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility( + toRegions(leftElementObserver.getVisibleEntries()) + ); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); + const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (tocOpenDepth === -1 && depth > 1) { + // toc-expand: false + el.classList.add("collapse"); + } else if ( + depth <= tocOpenDepth || + hasActiveChild || + prevSiblingIsActiveLink(el) + ) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/html_outputs/new_pages/quarto_files/libs/quarto-html/tippy.css b/html_outputs/new_pages/quarto_files/libs/quarto-html/tippy.css new file mode 100644 index 00000000..e6ae635c --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/html_outputs/new_pages/quarto_files/libs/quarto-html/tippy.umd.min.js b/html_outputs/new_pages/quarto_files/libs/quarto-html/tippy.umd.min.js new file mode 100644 index 00000000..ca292be3 --- /dev/null +++ b/html_outputs/new_pages/quarto_files/libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/html_outputs/new_pages/r_projects.html b/html_outputs/new_pages/r_projects.html index 95de96bf..678cb9b7 100644 --- a/html_outputs/new_pages/r_projects.html +++ b/html_outputs/new_pages/r_projects.html @@ -2,12 +2,12 @@ - + -The Epidemiologist R Handbook - 6  R projects +6  R projects – The Epidemiologist R Handbook + + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Characteristic

N

OR

+1

95% CI

+1

p-value

gender4,1671.000.88, 1.13>0.9
fever4,1671.000.85, 1.17>0.9
chills4,1671.030.89, 1.210.7
cough4,1671.150.97, 1.370.11
aches4,1670.930.76, 1.140.5
vomit4,1671.090.96, 1.230.2
age_cat4,167
+

+

+
    0-4
+

+
    5-9
+
0.940.77, 1.150.5
    10-14
+
1.150.93, 1.420.2
    15-19
+
0.990.80, 1.24>0.9
    20-29
+
1.030.84, 1.260.8
    30-49
+
1.070.85, 1.330.6
    50-69
+
0.680.41, 1.130.13
    70+
+
0.530.07, 3.200.5
1 +

OR = Odds Ratio, CI = Confidence Interval

+ + + + +
+
+

19.1.1 Cross-tabulation

+

The gtsummary package also allows us to quickly and easily create tables of counts. This can be useful for quickly summarising the data, and putting it in context with the regression we have carried out.

+
+
#Carry out our regression
+univ_tab <- linelist %>% 
+  dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest
+
+  tbl_uvregression(                         ## produce univariate table
+    method = glm,                           ## define regression want to run (generalised linear model)
+    y = outcome,                            ## define outcome variable
+    method.args = list(family = binomial),  ## define what type of glm want to run (logistic)
+    exponentiate = TRUE                     ## exponentiate to produce odds ratios (rather than log odds)
+  )
+
+#Create our cross tabulation
+cross_tab <- linelist %>%
+  dplyr::select(explanatory_vars, outcome) %>%   ## select variables of interest
+     tbl_summary(by = outcome)                   ## create summary table
+
+tbl_merge(tbls = list(cross_tab,
+                      univ_tab),
+          tab_spanner = c("Summary", "Univariate regression"))
+
+
+ + + +++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Characteristic

+

Summary

+
+

Univariate regression

+

0
+N = 1,825

+1

1
+N = 2,342

+1

N

OR

+2

95% CI

+2

p-value

gender916 (50%)1,174 (50%)4,1671.000.88, 1.13>0.9
fever1,485 (81%)1,906 (81%)4,1671.000.85, 1.17>0.9
chills353 (19%)465 (20%)4,1671.030.89, 1.210.7
cough1,553 (85%)2,033 (87%)4,1671.150.97, 1.370.11
aches189 (10%)228 (9.7%)4,1670.930.76, 1.140.5
vomit894 (49%)1,198 (51%)4,1671.090.96, 1.230.2
age_cat
+

+
4,167
+

+

+
    0-4338 (19%)427 (18%)
+

+
    5-9365 (20%)433 (18%)
+
0.940.77, 1.150.5
    10-14273 (15%)396 (17%)
+
1.150.93, 1.420.2
    15-19238 (13%)299 (13%)
+
0.990.80, 1.24>0.9
    20-29345 (19%)448 (19%)
+
1.030.84, 1.260.8
    30-49228 (12%)307 (13%)
+
1.070.85, 1.330.6
    50-6935 (1.9%)30 (1.3%)
+
0.680.41, 1.130.13
    70+3 (0.2%)2 (<0.1%)
+
0.530.07, 3.200.5
1 +

n (%)

2 +

OR = Odds Ratio, CI = Confidence Interval

+ +
-

Here is what the combined data frame looks like, printed nicely as an image with a function from flextable. The Tables for presentation explains how to customize such tables with flextable, or or you can use numerous other packages such as knitr or GT.

-
-
combined <- combined %>% 
-  flextable::qflextable()
+

There are many modifications you can make to this table output, such as adjusting the text labels, bolding rows by their p-value, etc. See tutorials here and elsewhere online.

+
-
-

Looping multiple univariate models

-

Below we present a method using glm() and tidy() for a more simple approach, see the section on gtsummary.

-

To run the models on several exposure variables to produce univariate odds ratios (i.e. not controlling for each other), you can use the approach below. It uses str_c() from stringr to create univariate formulas (see Characters and strings), runs the glm() regression on each formula, passes each glm() output to tidy() and finally collapses all the model outputs together with bind_rows() from tidyr. This approach uses map() from the package purrr to iterate - see the page on Iteration, loops, and lists for more information on this tool.

-
    -
  1. Create a vector of column names of the explanatory variables. We already have this as explanatory_vars from the Preparation section of this page.

  2. -
  3. Use str_c() to create multiple string formulas, with outcome on the left, and a column name from explanatory_vars on the right. The period . substitutes for the column name in explanatory_vars.

  4. -
-
-
explanatory_vars %>% str_c("outcome ~ ", .)
-
-
[1] "outcome ~ gender"  "outcome ~ fever"   "outcome ~ chills" 
-[4] "outcome ~ cough"   "outcome ~ aches"   "outcome ~ vomit"  
-[7] "outcome ~ age_cat"
-
-
-
    -
  1. Pass these string formulas to map() and set ~glm() as the function to apply to each input. Within glm(), set the regression formula as as.formula(.x) where .x will be replaced by the string formula defined in the step above. map() will loop over each of the string formulas, running regressions for each one.

  2. -
  3. The outputs of this first map() are passed to a second map() command, which applies tidy() to the regression outputs.

  4. -
  5. Finally the output of the second map() (a list of tidied data frames) is condensed with bind_rows(), resulting in one data frame with all the univariate results.

  6. -
-
-
models <- explanatory_vars %>%       # begin with variables of interest
-  str_c("outcome ~ ", .) %>%         # combine each variable into formula ("outcome ~ variable of interest")
-  
-  # iterate through each univariate formula
-  map(                               
-    .f = ~glm(                       # pass the formulas one-by-one to glm()
-      formula = as.formula(.x),      # within glm(), the string formula is .x
-      family = "binomial",           # specify type of glm (logistic)
-      data = linelist)) %>%          # dataset
-  
-  # tidy up each of the glm regression outputs from above
-  map(
-    .f = ~tidy(
-      .x, 
-      exponentiate = TRUE,           # exponentiate 
-      conf.int = TRUE)) %>%          # return confidence intervals
-  
-  # collapse the list of regression outputs in to one data frame
-  bind_rows() %>% 
-  
-  # round all numeric columns
-  mutate(across(where(is.numeric), round, digits = 2))
-
-

This time, the end object models is longer because it now represents the combined results of several univariate regressions. Click through to see all the rows of model.

-
-
-
- -
-
-

As before, we can create a counts table from the linelist for each explanatory variable, bind it to models, and make a nice table. We begin with the variables, and iterate through them with map(). We iterate through a user-defined function which involves creating a counts table with dplyr functions. Then the results are combined and bound with the models model results.

+
+
+

19.2 Stratified

+

Here we define stratified regression as the process of carrying out separate regression analyses on different “groups” of data.

+

Sometimes in your analysis, you will want to investigate whether or not there are different relationships between an outcome and variables, by different strata. This could be something like, a difference in gender, age group, or source of infection.

+

To do this, you will want to split your dataset into the strata of interest. For example, creating two separate datasets of gender == "f" and gender == "m", would be done by:

-
## for each explanatory variable
-univ_tab_base <- explanatory_vars %>% 
-  map(.f = 
-    ~{linelist %>%                ## begin with linelist
-        group_by(outcome) %>%     ## group data set by outcome
-        count(.data[[.x]]) %>%    ## produce counts for variable of interest
-        pivot_wider(              ## spread to wide format (as in cross-tabulation)
-          names_from = outcome,
-          values_from = n) %>% 
-        drop_na(.data[[.x]]) %>%         ## drop rows with missings
-        rename("variable" = .x) %>%      ## change variable of interest column to "variable"
-        mutate(variable = as.character(variable))} ## convert to character, else non-dichotomous (categorical) variables come out as factor and cant be merged
-      ) %>% 
-  
-  ## collapse the list of count outputs in to one data frame
-  bind_rows() %>% 
-  
-  ## merge with the outputs of the regression 
-  bind_cols(., models) %>% 
-  
-  ## only keep columns interested in 
-  select(term, 2:3, estimate, conf.low, conf.high, p.value) %>% 
-  
-  ## round decimal places
-  mutate(across(where(is.numeric), round, digits = 2))
+
f_linelist <- linelist %>%
+     filter(gender == 0) %>%                 ## subset to only where the gender == "f"
+  dplyr::select(explanatory_vars, outcome)     ## select variables of interest
+     
+m_linelist <- linelist %>%
+     filter(gender == 1) %>%                 ## subset to only where the gender == "f"
+  dplyr::select(explanatory_vars, outcome)     ## select variables of interest
-

Below is what the data frame looks like. See the page on Tables for presentation for ideas on how to further convert this table to pretty HTML output (e.g. with flextable).

+

Once this has been done, you can carry out your regression in either base R or gtsummary.

+
+

19.2.1 base R

+

To carry this out in base R, you run two different regressions, one for where gender == "f" and gender == "m".

-
-
- +
#Run model for f
+f_model <- glm(outcome ~ vomit, family = "binomial", data = f_linelist) %>% 
+     tidy(exponentiate = TRUE, conf.int = TRUE) %>%        # exponentiate and produce CIs
+     mutate(across(where(is.numeric), round, digits = 2)) %>%  # round all numeric columns
+     mutate(gender = "f")                                      # create a column which identifies these results as using the f dataset
+ 
+#Run model for m
+m_model <- glm(outcome ~ vomit, family = "binomial", data = m_linelist) %>% 
+     tidy(exponentiate = TRUE, conf.int = TRUE) %>%        # exponentiate and produce CIs
+     mutate(across(where(is.numeric), round, digits = 2)) %>%  # round all numeric columns
+     mutate(gender = "m")                                      # create a column which identifies these results as using the m dataset
+
+#Combine the results
+rbind(f_model,
+      m_model)
+
+
# A tibble: 4 × 8
+  term        estimate std.error statistic p.value conf.low conf.high gender
+  <chr>          <dbl>     <dbl>     <dbl>   <dbl>    <dbl>     <dbl> <chr> 
+1 (Intercept)     1.25      0.06      3.56    0        1.11      1.42 f     
+2 vomit           1.05      0.09      0.58    0.56     0.89      1.25 f     
+3 (Intercept)     1.21      0.06      3.04    0        1.07      1.36 m     
+4 vomit           1.13      0.09      1.38    0.17     0.95      1.34 m     
- -
-
-

gtsummary package

-

Below we present the use of tbl_uvregression() from the gtsummary package. Just like in the page on Descriptive tables, gtsummary functions do a good job of running statistics and producing professional-looking outputs. This function produces a table of univariate regression results.

-

We select only the necessary columns from the linelist (explanatory variables and the outcome variable) and pipe them into tbl_uvregression(). We are going to run univariate regression on each of the columns we defined as explanatory_vars in the data Preparation section (gender, fever, chills, cough, aches, vomit, and age_cat).

-

Within the function itself, we provide the method = as glm (no quotes), the y = outcome column (outcome), specify to method.args = that we want to run logistic regression via family = binomial, and we tell it to exponentiate the results.

-

The output is HTML and contains the counts

+
+

19.2.2 gtsummary

+

The same approach is repeated using gtsummary, however it is easier to produce publication ready tables with gtsummary and compare the two tables with the function tbl_merge().

-
univ_tab <- linelist %>% 
-  dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest
-
-  tbl_uvregression(                         ## produce univariate table
-    method = glm,                           ## define regression want to run (generalised linear model)
-    y = outcome,                            ## define outcome variable
-    method.args = list(family = binomial),  ## define what type of glm want to run (logistic)
-    exponentiate = TRUE                     ## exponentiate to produce odds ratios (rather than log odds)
-  )
-
-## view univariate results table 
-univ_tab
+
#Run model for f
+f_model_gt <- f_linelist %>% 
+     dplyr::select(vomit, outcome) %>% ## select variables of interest
+     tbl_uvregression(                         ## produce univariate table
+          method = glm,                           ## define regression want to run (generalised linear model)
+          y = outcome,                            ## define outcome variable
+          method.args = list(family = binomial),  ## define what type of glm want to run (logistic)
+          exponentiate = TRUE                     ## exponentiate to produce odds ratios (rather than log odds)
+     )
+
+#Run model for m
+m_model_gt <- m_linelist %>% 
+     dplyr::select(vomit, outcome) %>% ## select variables of interest
+     tbl_uvregression(                         ## produce univariate table
+          method = glm,                           ## define regression want to run (generalised linear model)
+          y = outcome,                            ## define outcome variable
+          method.args = list(family = binomial),  ## define what type of glm want to run (logistic)
+          exponentiate = TRUE                     ## exponentiate to produce odds ratios (rather than log odds)
+     )
+
+#Combine gtsummary tables
+f_and_m_table <- tbl_merge(
+     tbls = list(f_model_gt,
+                 m_model_gt),
+     tab_spanner = c("Female",
+                     "Male")
+)
+
+#Print
+f_and_m_table
-
-
- - +
-----+++++++++ - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - + + @@ -1798,22 +3175,15 @@

gtsummary - -

There are many modifications you can make to this table output, such as adjusting the text labels, bolding rows by their p-value, etc. See tutorials here and elsewhere online.

- -
-

19.3 Stratified

-

Stratified analysis is currently still being worked on for gtsummary, this page will be updated in due course.

-
-
-

19.4 Multivariable

+
+

19.3 Multivariable

For multivariable analysis, we again present two approaches:

    -
  • glm() and tidy()
    +
  • glm() and tidy().
  • -
  • gtsummary package
  • +
  • gtsummary package.

The workflow is similar for each and only the last step of pulling together a final table is different.

@@ -1821,9 +3191,9 @@

Conduct m

Here we use glm() but add more variables to the right side of the equation, separated by plus symbols (+).

To run the model with all of our explanatory variables we would run:

-
mv_reg <- glm(outcome ~ gender + fever + chills + cough + aches + vomit + age_cat, family = "binomial", data = linelist)
-
-summary(mv_reg)
+
mv_reg <- glm(outcome ~ gender + fever + chills + cough + aches + vomit + age_cat, family = "binomial", data = linelist)
+
+summary(mv_reg)

 Call:
@@ -1856,28 +3226,28 @@ 

Conduct m Number of Fisher Scoring iterations: 4

-

If you want to include two variables and an interaction between them you can separate them with an asterisk * instead of a +. Separate them with a colon : if you are only specifying the interaction. For example:

+

If you want to include two variables and an interaction between them you can separate them with an asterisk * instead of a +. Separate them with a colon : if you are only specifying the interaction. For example:

-
glm(outcome ~ gender + age_cat * fever, family = "binomial", data = linelist)
+
glm(outcome ~ gender + age_cat * fever, family = "binomial", data = linelist)

Optionally, you can use this code to leverage the pre-defined vector of column names and re-create the above command using str_c(). This might be useful if your explanatory variable names are changing, or you don’t want to type them all out again.

-
## run a regression with all variables of interest 
-mv_reg <- explanatory_vars %>%  ## begin with vector of explanatory column names
-  str_c(collapse = "+") %>%     ## combine all names of the variables of interest separated by a plus
-  str_c("outcome ~ ", .) %>%    ## combine the names of variables of interest with outcome in formula style
-  glm(family = "binomial",      ## define type of glm as logistic,
-      data = linelist)          ## define your dataset
+
## run a regression with all variables of interest 
+mv_reg <- explanatory_vars %>%  ## begin with vector of explanatory column names
+  str_c(collapse = "+") %>%     ## combine all names of the variables of interest separated by a plus
+  str_c("outcome ~ ", .) %>%    ## combine the names of variables of interest with outcome in formula style
+  glm(family = "binomial",      ## define type of glm as logistic,
+      data = linelist)          ## define your dataset

Building the model

You can build your model step-by-step, saving various models that include certain explanatory variables. You can compare these models with likelihood-ratio tests using lrtest() from the package lmtest, as below:

NOTE: Using base anova(model1, model2, test = "Chisq) produces the same results

-
model1 <- glm(outcome ~ age_cat, family = "binomial", data = linelist)
-model2 <- glm(outcome ~ age_cat + gender, family = "binomial", data = linelist)
-
-lmtest::lrtest(model1, model2)
+
model1 <- glm(outcome ~ age_cat, family = "binomial", data = linelist)
+model2 <- glm(outcome ~ age_cat + gender, family = "binomial", data = linelist)
+
+lmtest::lrtest(model1, model2)
Likelihood ratio test
 
@@ -1890,26 +3260,26 @@ 

Building the

Another option is to take the model object and apply the step() function from the stats package. Specify which variable selection direction you want use when building the model.

-
## choose a model using forward selection based on AIC
-## you can also do "backward" or "both" by adjusting the direction
-final_mv_reg <- mv_reg %>%
-  step(direction = "forward", trace = FALSE)
+
## choose a model using forward selection based on AIC
+## you can also do "backward" or "both" by adjusting the direction
+final_mv_reg <- mv_reg %>%
+  step(direction = "forward", trace = FALSE)

You can also turn off scientific notation in your R session, for clarity:

-
options(scipen=999)
+
options(scipen=999)

As described in the section on univariate analysis, pass the model output to tidy() to exponentiate the log odds and CIs. Finally we round all numeric columns to two decimal places. Scroll through to see all the rows.

-
mv_tab_base <- final_mv_reg %>% 
-  broom::tidy(exponentiate = TRUE, conf.int = TRUE) %>%  ## get a tidy dataframe of estimates 
-  mutate(across(where(is.numeric), round, digits = 2))          ## round 
+
mv_tab_base <- final_mv_reg %>% 
+  broom::tidy(exponentiate = TRUE, conf.int = TRUE) %>%  ## get a tidy dataframe of estimates 
+  mutate(across(where(is.numeric), round, digits = 2))          ## round 

Here is what the resulting data frame looks like:

-
- +
+
@@ -1919,33 +3289,32 @@

Building the

Combine univariate and multivariable

Combine with gtsummary

-

The gtsummary package provides the tbl_regression() function, which will take the outputs from a regression (glm() in this case) and produce an nice summary table.

+

The gtsummary package provides the tbl_regression() function, which will take the outputs from a regression (glm() in this case) and produce a nice summary table.

-
## show results table of final regression 
-mv_tab <- tbl_regression(final_mv_reg, exponentiate = TRUE)
+
## show results table of final regression 
+mv_tab <- tbl_regression(final_mv_reg, exponentiate = TRUE)

Let’s see the table:

-
mv_tab
+
mv_tab
-
-
- -

CharacteristicNOR195% CI1p-value

Characteristic

+

Female

+
+

Male

+

N

OR

+1

95% CI

+1

p-value

N

OR

+1

95% CI

+1

p-value

gender4,1671.000.88, 1.13>0.9
fever4,1671.000.85, 1.17>0.9
chills4,1671.030.89, 1.210.7
cough4,1671.150.97, 1.370.11
aches4,1670.930.76, 1.140.5
vomit4,1671.090.96, 1.230.2
age_cat4,167
-

-

-
    0-4
-

-
    5-9
-
0.940.77, 1.150.5
    10-14
-
1.150.93, 1.420.2
    15-19
-
0.990.80, 1.24>0.9
    20-29
-
1.030.84, 1.260.8
    30-49
-
1.070.85, 1.330.6
    50-69
-
0.680.41, 1.130.13
    70+
-
0.530.07, 3.200.52,0771.050.89, 1.250.62,0901.130.95, 1.340.2
1 OR = Odds Ratio, CI = Confidence Interval
1 +

OR = Odds Ratio, CI = Confidence Interval

+
@@ -2379,54 +3757,60 @@

Combine

- - - - + + + + - + - + - + - + - + - + - @@ -2434,55 +3818,56 @@

Combine

- + - + - + - + - + - + - + - + - + @@ -2491,32 +3876,30 @@

Combine -

You can also combine several different output tables produced by gtsummary with the tbl_merge() function. We now combine the multivariable results with the gtsummary univariate results that we created above:

-
## combine with univariate results 
-tbl_merge(
-  tbls = list(univ_tab, mv_tab),                          # combine
-  tab_spanner = c("**Univariate**", "**Multivariable**")) # set header names
+
## combine with univariate results 
+tbl_merge(
+  tbls = list(univ_tab, mv_tab),                          # combine
+  tab_spanner = c("**Univariate**", "**Multivariable**")) # set header names
-
-
- -

CharacteristicOR195% CI1p-value

Characteristic

OR

+1

95% CI

+1

p-value

gender 1.000.88, 1.140.88, 1.14 >0.9
fever 1.000.86, 1.180.86, 1.18 >0.9
chills 1.030.89, 1.210.89, 1.21 0.7
cough 1.150.96, 1.370.96, 1.37 0.12
aches 0.930.76, 1.140.76, 1.14 0.5
vomit 1.090.96, 1.230.96, 1.23 0.2
age_cat

+


    0-4
    5-9 0.940.77, 1.150.77, 1.15 0.5
    10-14 1.150.93, 1.410.93, 1.41 0.2
    15-19 0.990.79, 1.240.79, 1.24 >0.9
    20-29 1.030.84, 1.260.84, 1.26 0.8
    30-49 1.060.85, 1.330.85, 1.33 0.6
    50-69 0.680.40, 1.130.40, 1.13 0.14
    70+ 0.520.07, 3.190.07, 3.19 0.5
1 OR = Odds Ratio, CI = Confidence Interval1 +

OR = Odds Ratio, CI = Confidence Interval

+
@@ -2954,18 +4346,36 @@

Combine

- - - + + + - - - - - - - + + + + + + + @@ -2973,60 +4383,60 @@

Combine

- + - + - + - + - + - + - + - + - + - + - + - + @@ -3034,13 +4444,13 @@

Combine

- - @@ -3050,11 +4460,11 @@

Combine

- + - + @@ -3063,10 +4473,10 @@

Combine

- + - + @@ -3074,10 +4484,10 @@

Combine

- + - + @@ -3085,10 +4495,10 @@

Combine

- + - + @@ -3096,10 +4506,10 @@

Combine

- + - + @@ -3107,10 +4517,10 @@

Combine

- + - + @@ -3118,10 +4528,10 @@

Combine

- + - + @@ -3129,15 +4539,16 @@

Combine

- + - + - + @@ -3146,36 +4557,35 @@

Combine -

Combine with dplyr

An alternative way of combining the glm()/tidy() univariate and multivariable outputs is with the dplyr join functions.

    -
  • Join the univariate results from earlier (univ_tab_base, which contains counts) with the tidied multivariable results mv_tab_base
    +
  • Join the univariate results from earlier (univ_tab_base, which contains counts) with the tidied multivariable results mv_tab_base.
  • -
  • Use select() to keep only the columns we want, specify their order, and re-name them
    +
  • Use select() to keep only the columns we want, specify their order, and re-name them.
  • -
  • Use round() with two decimal places on all the column that are class Double
  • +
  • Use round() with two decimal places on all the column that are class Double.
-
## combine univariate and multivariable tables 
-left_join(univ_tab_base, mv_tab_base, by = "term") %>% 
-  ## choose columns and rename them
-  select( # new name =  old name
-    "characteristic" = term, 
-    "recovered"      = "0", 
-    "dead"           = "1", 
-    "univ_or"        = estimate.x, 
-    "univ_ci_low"    = conf.low.x, 
-    "univ_ci_high"   = conf.high.x,
-    "univ_pval"      = p.value.x, 
-    "mv_or"          = estimate.y, 
-    "mvv_ci_low"     = conf.low.y, 
-    "mv_ci_high"     = conf.high.y,
-    "mv_pval"        = p.value.y 
-  ) %>% 
-  mutate(across(where(is.double), round, 2))   
+
## combine univariate and multivariable tables 
+left_join(univ_tab_base, mv_tab_base, by = "term") %>% 
+  ## choose columns and rename them
+  select( # new name =  old name
+    "characteristic" = term, 
+    "recovered"      = "0", 
+    "dead"           = "1", 
+    "univ_or"        = estimate.x, 
+    "univ_ci_low"    = conf.low.x, 
+    "univ_ci_high"   = conf.high.x,
+    "univ_pval"      = p.value.x, 
+    "mv_or"          = estimate.y, 
+    "mvv_ci_low"     = conf.low.y, 
+    "mv_ci_high"     = conf.high.y,
+    "mv_pval"        = p.value.y 
+  ) %>% 
+  mutate(across(where(is.double), round, 2))   
# A tibble: 20 × 11
    characteristic recovered  dead univ_or univ_ci_low univ_ci_high univ_pval
@@ -3208,8 +4618,8 @@ 

Combine with

-
-

19.5 Forest plot

+
+

19.4 Forest plot

This section shows how to produce a plot with the outputs of your regression. There are two options, you can build a plot yourself using ggplot2 or use a meta-package called easystats (a package that includes many packages).

See the page on ggplot basics if you are unfamiliar with the ggplot2 plotting package.

@@ -3217,38 +4627,38 @@

ggplot2 package

You can build a forest plot with ggplot() by plotting elements of the multivariable regression results. Add the layers of the plots using these “geoms”:

    -
  • estimates with geom_point()
    +
  • estimates with geom_point().
  • -
  • confidence intervals with geom_errorbar()
    +
  • confidence intervals with geom_errorbar().
  • -
  • a vertical line at OR = 1 with geom_vline()
  • +
  • a vertical line at OR = 1 with geom_vline().

Before plotting, you may want to use fct_relevel() from the forcats package to set the order of the variables/levels on the y-axis. ggplot() may display them in alpha-numeric order which would not work well for these age category values (“30” would appear before “5”). See the page on Factors for more details.

-
## remove the intercept term from your multivariable results
-mv_tab_base %>% 
-  
-  #set order of levels to appear along y-axis
-  mutate(term = fct_relevel(
-    term,
-    "vomit", "gender", "fever", "cough", "chills", "aches",
-    "age_cat5-9", "age_cat10-14", "age_cat15-19", "age_cat20-29",
-    "age_cat30-49", "age_cat50-69", "age_cat70+")) %>%
-  
-  # remove "intercept" row from plot
-  filter(term != "(Intercept)") %>% 
-  
-  ## plot with variable on the y axis and estimate (OR) on the x axis
-  ggplot(aes(x = estimate, y = term)) +
-  
-  ## show the estimate as a point
-  geom_point() + 
-  
-  ## add in an error bar for the confidence intervals
-  geom_errorbar(aes(xmin = conf.low, xmax = conf.high)) + 
-  
-  ## show where OR = 1 is for reference as a dashed line
-  geom_vline(xintercept = 1, linetype = "dashed")
+
## remove the intercept term from your multivariable results
+mv_tab_base %>% 
+  
+  #set order of levels to appear along y-axis
+  mutate(term = fct_relevel(
+    term,
+    "vomit", "gender", "fever", "cough", "chills", "aches",
+    "age_cat5-9", "age_cat10-14", "age_cat15-19", "age_cat20-29",
+    "age_cat30-49", "age_cat50-69", "age_cat70+")) %>%
+  
+  # remove "intercept" row from plot
+  filter(term != "(Intercept)") %>% 
+  
+  ## plot with variable on the y axis and estimate (OR) on the x axis
+  ggplot(aes(x = estimate, y = term)) +
+  
+  ## show the estimate as a point
+  geom_point() + 
+  
+  ## add in an error bar for the confidence intervals
+  geom_errorbar(aes(xmin = conf.low, xmax = conf.high)) + 
+  
+  ## show where OR = 1 is for reference as a dashed line
+  geom_vline(xintercept = 1, linetype = "dashed")
@@ -3264,12 +4674,12 @@

easy

An alternative, if you do not want to the fine level of control that ggplot2 provides, is to use a combination of easystats packages.

The function model_parameters() from the parameters package does the equivalent of the broom package function tidy(). The see package then accepts those outputs and creates a default forest plot as a ggplot() object.

-
pacman::p_load(easystats)
-
-## remove the intercept term from your multivariable results
-final_mv_reg %>% 
-  model_parameters(exponentiate = TRUE) %>% 
-  plot()
+
pacman::p_load(easystats)
+
+## remove the intercept term from your multivariable results
+final_mv_reg %>% 
+  model_parameters(exponentiate = TRUE) %>% 
+  plot()
@@ -3278,9 +4688,64 @@

easy

-

+
+

19.5 Model performance

+

Once you have built your regression models, you may want to assess how well the model has fit the data. There are many different approaches to do this, and many different metrics with which to assess your model fit, and how it compares with other model formulations. How you assess your model fit will depend on your model, the data, and the context in which you are conducting your work.

+

While there are many different functions, and many different packages, to assess model fit, one package that nicely combines several different metrics and approaches into a single source is the performance package. This package allows you to assess model assumptions (such as linearity, homogeneity, highlight outliers, etc.) and check how well the model performs (Akaike Information Criterion values, R2, RMSE, etc) with a few simple functions.

+

Unfortunately, we are unable to use this package with gtsummary, but it readily accepts objects generated by other packages such as stats, lmerMod and tidymodels. Here we will demonstrate its application using the function glm() for a multivariable regression. To do this we can use the function performance() to assess model fit, and compare_perfomrance() to compare the two models.

+
+
#Load in packages
+pacman::p_load(performance)
+
+#Set up regression models
+regression_one <- linelist %>%
+     select(outcome, gender, fever, chills, cough) %>%
+     glm(formula = outcome ~ .,
+         family = binomial)
+
+regression_two <- linelist %>%
+     select(outcome, days_onset_hosp, aches, vomit, age_years) %>%
+     glm(formula = outcome ~ .,
+         family = binomial)
+
+#Assess model fit
+performance(regression_one)
+
+
# Indices of model performance
+
+AIC      |     AICc |      BIC | Tjur's R2 |  RMSE | Sigma | Log_loss | Score_log |   PCP
+-----------------------------------------------------------------------------------------
+5719.746 | 5719.760 | 5751.421 | 6.342e-04 | 0.496 | 1.000 |    0.685 |      -Inf | 0.508
+
+
performance(regression_two)
+
+
# Indices of model performance
+
+AIC      |     AICc |      BIC | Tjur's R2 |  RMSE | Sigma | Log_loss | Score_log |   PCP
+-----------------------------------------------------------------------------------------
+5411.752 | 5411.767 | 5443.187 |     0.012 | 0.493 | 1.000 |    0.680 |      -Inf | 0.513
+
+
#Compare model fit
+compare_performance(regression_one,
+                    regression_two)
+
+
When comparing models, please note that probably not all models were fit
+  from same data.
+
+
+
# Comparison of Model Performance Indices
+
+Name           | Model |  AIC (weights) | AICc (weights) |  BIC (weights) | Tjur's R2 |  RMSE | Sigma | Log_loss | Score_log |   PCP
+------------------------------------------------------------------------------------------------------------------------------------
+regression_one |   glm | 5719.7 (<.001) | 5719.8 (<.001) | 5751.4 (<.001) | 6.342e-04 | 0.496 | 1.000 |    0.685 |      -Inf | 0.508
+regression_two |   glm | 5411.8 (>.999) | 5411.8 (>.999) | 5443.2 (>.999) |     0.012 | 0.493 | 1.000 |    0.680 |      -Inf | 0.513
+
+
+

For further reading on the performance package, and the model tests you can carry out, see their github.

+ +

19.6 Resources

The content of this page was informed by these resources and vignettes online:

@@ -3472,18 +4937,7 @@

{ - lightboxQuarto.on('slide_before_load', (data) => { - const { slideIndex, slideNode, slideConfig, player, trigger } = data; - const href = trigger.getAttribute('href'); - if (href !== null) { - const imgEl = window.document.querySelector(`a[href="${href}"] img`); - if (imgEl !== null) { - const srcAttr = imgEl.getAttribute("src"); - if (srcAttr && srcAttr.startsWith("data:")) { - slideConfig.href = srcAttr; + diff --git a/html_outputs/new_pages/reportfactory.html b/html_outputs/new_pages/reportfactory.html index 0ad4d6fb..6849fd30 100644 --- a/html_outputs/new_pages/reportfactory.html +++ b/html_outputs/new_pages/reportfactory.html @@ -2,12 +2,12 @@ - + -The Epidemiologist R Handbook - 41  Organizing routine reports +41  Organizing routine reports – The Epidemiologist R Handbook -

CharacteristicUnivariateMultivariable

Characteristic

+

Univariate

+
+

Multivariable

+
NOR195% CI1p-valueOR195% CI1p-value

N

OR

+1

95% CI

+1

p-value

OR

+1

95% CI

+1

p-value

gender 4,167 1.000.88, 1.130.88, 1.13 >0.9 1.000.88, 1.140.88, 1.14 >0.9
fever 4,167 1.000.85, 1.170.85, 1.17 >0.9 1.000.86, 1.180.86, 1.18 >0.9
chills 4,167 1.030.89, 1.210.89, 1.21 0.7 1.030.89, 1.210.89, 1.21 0.7
cough 4,167 1.150.97, 1.370.97, 1.37 0.11 1.150.96, 1.370.96, 1.37 0.12
aches 4,167 0.930.76, 1.140.76, 1.14 0.5 0.930.76, 1.140.76, 1.14 0.5
vomit 4,167 1.090.96, 1.230.96, 1.23 0.2 1.090.96, 1.230.96, 1.23 0.2
4,167

+




+






0.940.77, 1.150.77, 1.15 0.5 0.940.77, 1.150.77, 1.15 0.5

1.150.93, 1.420.93, 1.42 0.2 1.150.93, 1.410.93, 1.41 0.2

0.990.80, 1.240.80, 1.24 >0.9 0.990.79, 1.240.79, 1.24 >0.9

1.030.84, 1.260.84, 1.26 0.8 1.030.84, 1.260.84, 1.26 0.8

1.070.85, 1.330.85, 1.33 0.6 1.060.85, 1.330.85, 1.33 0.6

0.680.41, 1.130.41, 1.13 0.13 0.680.40, 1.130.40, 1.13 0.14

0.530.07, 3.200.07, 3.20 0.5 0.520.07, 3.190.07, 3.19 0.5
1 OR = Odds Ratio, CI = Confidence Interval1 +

OR = Odds Ratio, CI = Confidence Interval

+
@@ -1486,10 +1572,21 @@

Chi-squared

- - - - + + + + @@ -1524,10 +1621,12 @@

Chi-squared

- + - + @@ -1536,7 +1635,6 @@

Chi-squared -

T-tests

@@ -1549,27 +1647,26 @@

T-tests

by = outcome) %>% # specify the grouping variable add_p(age_years ~ "t.test") # specify what tests to perform
-
1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.
+
1323 missing rows in the "outcome" column have been removed.
-
-
- -

CharacteristicDeath, N = 2,5821Recover, N = 1,9831p-value2

Characteristic

Death
+N = 2,582

+1

Recover
+N = 1,983

+1

p-value

+2
1 n (%)1 +

n (%)

2 Pearson’s Chi-squared test2 +

Pearson’s Chi-squared test

+
@@ -2003,10 +2109,21 @@

T-tests

- - - - + + + + @@ -2025,10 +2142,12 @@

T-tests

- + - + @@ -2037,7 +2156,6 @@

T-tests

-

Wilcoxon rank sum test

@@ -2050,27 +2168,26 @@

Wilcox by = outcome) %>% # specify the grouping variable add_p(age_years ~ "wilcox.test") # specify what test to perform (default so could leave brackets empty)
-
1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.
+
1323 missing rows in the "outcome" column have been removed.
-
-
- -

CharacteristicDeath, N = 2,5821Recover, N = 1,9831p-value2

Characteristic

Death
+N = 2,582

+1

Recover
+N = 1,983

+1

p-value

+2
1 Mean (SD)1 +

Mean (SD)

2 Welch Two Sample t-test2 +

Welch Two Sample t-test

+
@@ -2504,10 +2630,21 @@

Wilcox

- - - - + + + + @@ -2526,10 +2663,12 @@

Wilcox

- + - + @@ -2538,7 +2677,6 @@

Wilcox -

Kruskal-wallis test

@@ -2551,27 +2689,26 @@

Kruskal-w by = outcome) %>% # specify the grouping variable add_p(age_years ~ "kruskal.test") # specify what test to perform
-
1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.
+
1323 missing rows in the "outcome" column have been removed.
-
-
- -

CharacteristicDeath, N = 2,5821Recover, N = 1,9831p-value2

Characteristic

Death
+N = 2,582

+1

Recover
+N = 1,983

+1

p-value

+2
1 Median (IQR)1 +

Median (Q1, Q3)

2 Wilcoxon rank sum test2 +

Wilcoxon rank sum test

+
@@ -3005,10 +3151,21 @@

Kruskal-w

- - - - + + + + @@ -3027,10 +3184,12 @@

Kruskal-w

- + - + @@ -3039,7 +3198,6 @@

Kruskal-w - @@ -3220,7 +3378,13 @@

18.6 Resources

Much of the information in this page is adapted from these resources and vignettes online:

-

gtsummary dplyr corrr sthda correlation

+

gtsummary

+

dplyr

+

corrr

+

sthda correlation

+

Resources for statistical theory

+

Causal Inference: What If

+

Discovering statistics

@@ -3405,18 +3569,7 @@

{ - lightboxQuarto.on('slide_before_load', (data) => { - const { slideIndex, slideNode, slideConfig, player, trigger } = data; - const href = trigger.getAttribute('href'); - if (href !== null) { - const imgEl = window.document.querySelector(`a[href="${href}"] img`); - if (imgEl !== null) { - const srcAttr = imgEl.getAttribute("src"); - if (srcAttr && srcAttr.startsWith("data:")) { - slideConfig.href = srcAttr; + diff --git a/html_outputs/new_pages/survey_analysis.html b/html_outputs/new_pages/survey_analysis.html index ef3a3a31..ba1b4096 100644 --- a/html_outputs/new_pages/survey_analysis.html +++ b/html_outputs/new_pages/survey_analysis.html @@ -2,12 +2,12 @@ - + -The Epidemiologist R Handbook - 26  Survey analysis +26  Survey analysis – The Epidemiologist R Handbook -

CharacteristicDeath, N = 2,5821Recover, N = 1,9831p-value2

Characteristic

Death
+N = 2,582

+1

Recover
+N = 1,983

+1

p-value

+2
1 Median (IQR)1 +

Median (Q1, Q3)

2 Kruskal-Wallis rank sum test2 +

Kruskal-Wallis rank sum test

+
++++++ - - - - + + + + @@ -2027,23 +2089,28 @@

arrived

- + - + - + - + + + + @@ -2052,7 +2119,6 @@

- @@ -2062,16 +2128,16 @@

26.9.1 Survey package

-
ratio <- svyratio(~died, 
-         denominator = ~obstime, 
-         design = base_survey_design)
-
-ci <- confint(ratio)
-
-cbind(
-  ratio$ratio * 10000, 
-  ci * 10000
-)
+
ratio <- svyratio(~died, 
+         denominator = ~obstime, 
+         design = base_survey_design)
+
+ci <- confint(ratio)
+
+cbind(
+  ratio$ratio * 10000, 
+  ci * 10000
+)
      obstime    2.5 %   97.5 %
 died 5.981922 1.194294 10.76955
@@ -2081,14 +2147,14 @@

26.9.2 Srvyr package

-
survey_design %>% 
-  ## survey ratio used to account for observation time 
-  summarise(
-    mortality = survey_ratio(
-      as.numeric(died) * 10000, 
-      obstime, 
-      vartype = "ci")
-    )
+
survey_design %>% 
+  ## survey ratio used to account for observation time 
+  summarise(
+    mortality = survey_ratio(
+      as.numeric(died) * 10000, 
+      obstime, 
+      vartype = "ci")
+    )
# A tibble: 1 × 3
   mortality mortality_low mortality_upp
@@ -2096,165 +2162,1277 @@ 

- -
-

26.10 Resources

-

UCLA stats page

-

Analyze survey data free

-

srvyr packge

-

gtsummary package

-

EPIET survey case studies

+
+

26.10 Weighted regression

+

Another tool we can use to analyse our survey data is to use weighted regression. This allows us to carry out to account for the survey design in our regression in order to avoid biases that may be introduced from the survey process.

+

To carry out a univariate regression, we can use the packages survey for the function svyglm() and the package gtsummary which allows us to call svyglm() inside the function tbl_uvregression. To do this we first use the survey_design object created above. This is then provided to the function tbl_uvregression() as in the Univariate and multivariable regression chapter. We then make one key change, we change method = glm to method = survey::svyglm in order to carry out our survey weighted regression.

+

Here we will be using the previously created object survey_design to predict whether the value in the column died is TRUE, using the columns malaria_treatment, bednet, and age_years.

+
+
survey_design %>%
+     tbl_uvregression(                             #Carry out a univariate regression, if we wanted a multivariable regression we would use tbl_
+          method = survey::svyglm,                 #Set this to survey::svyglm to carry out our weighted regression on the survey data
+          y = died,                                #The column we are trying to predict
+          method.args = list(family = binomial),   #The family, we are carrying out a logistic regression so we want the family as binomial
+          include = c(malaria_treatment,           #These are the columns we want to evaluate
+                      bednet,
+                      age_years),
+          exponentiate = T                         #To transform the log odds to odds ratio for easier interpretation
+     )
+
+
+ + +

CharacteristicWeighted total (N)Weighted Count195%CI

Characteristic

Weighted total (N)

Weighted count

+1

95% CI

+2
1,482,457 761,799 (51%)40.9-61.741%, 62%
left 1,482,457 701,199 (47%)39.2-55.539%, 56%
died 1,482,457 76,213 (5.1%)2.1-12.12.1%, 12%
1 n (%)1 +

n (%)

2 +

CI = Confidence Interval

+++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Characteristic

N

OR

+1

95% CI

+1

p-value

malaria_treatment1,357,145
+

+

+
    FALSE
+

+
    TRUE
+
1.120.33, 3.820.8
bednet1,482,457
+

+

+
    FALSE
+

+
    TRUE
+
0.500.13, 1.880.3
age_years1,482,4571.011.00, 1.020.013
1 +

OR = Odds Ratio, CI = Confidence Interval

+ +
+
+
+

If we wanted to carry out a multivariable regression, we would have to first use the function svyglm() and pipe (%>%) the results into the function tbl_regression. Note that we need to specify the formula.

+
+
survey_design %>%
+     svyglm(formula = died ~ malaria_treatment + 
+                 bednet + 
+                 age_years,
+            family = binomial) %>%                   #The family, we are carrying out a logistic regression so we want the family as binomial
+     tbl_regression( 
+          exponentiate = T                           #To transform the log odds to odds ratio for easier interpretation                            
+     )
+
+
+ + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Characteristic

OR

+1

95% CI

+1

p-value

malaria_treatment
+

+

+
    FALSE
+
    TRUE1.110.28, 4.400.9
bednet
+

+

+
    FALSE
+
    TRUE0.620.12, 3.210.5
age_years1.011.00, 1.020.018
1 +

OR = Odds Ratio, CI = Confidence Interval

+ +
+
+
+ +
+
+

26.11 Resources

+

UCLA stats page

+

Analyze survey data free

+

srvyr packge

+

gtsummary package

+

EPIET survey case studies

+

Analyzing Complex Survey Data

+ + +
+ + + diff --git a/html_outputs/new_pages/survey_analysis_files/figure-html/weighted_age_pyramid-1.png b/html_outputs/new_pages/survey_analysis_files/figure-html/weighted_age_pyramid-1.png index b8c42585..782bba3d 100644 Binary files a/html_outputs/new_pages/survey_analysis_files/figure-html/weighted_age_pyramid-1.png and b/html_outputs/new_pages/survey_analysis_files/figure-html/weighted_age_pyramid-1.png differ diff --git a/html_outputs/new_pages/survival_analysis.html b/html_outputs/new_pages/survival_analysis.html index 8cab565c..4fd9d497 100644 --- a/html_outputs/new_pages/survival_analysis.html +++ b/html_outputs/new_pages/survival_analysis.html @@ -2,12 +2,12 @@ - + -The Epidemiologist R Handbook - 27  Survival analysis +27  Survival analysis – The Epidemiologist R Handbook

Age Category/Gender

f

m

NA_

Total

0-4

640 (22.8%)

416 (14.8%)

39 (14.0%)

1,095 (18.6%)

5-9

641 (22.8%)

412 (14.7%)

42 (15.1%)

1,095 (18.6%)

10-14

518 (18.5%)

383 (13.7%)

40 (14.4%)

941 (16.0%)

15-19

359 (12.8%)

364 (13.0%)

20 (7.2%)

743 (12.6%)

20-29

468 (16.7%)

575 (20.5%)

30 (10.8%)

1,073 (18.2%)

30-49

179 (6.4%)

557 (19.9%)

18 (6.5%)

754 (12.8%)

50-69

2 (0.1%)

91 (3.2%)

2 (0.7%)

95 (1.6%)

70+

0 (0.0%)

5 (0.2%)

1 (0.4%)

6 (0.1%)

0 (0.0%)

0 (0.0%)

86 (30.9%)

86 (1.5%)

+

Age Category/Gender

f

m

NA_

Total

0-4

640 (22.8%)

416 (14.8%)

39 (14.0%)

1,095 (18.6%)

5-9

641 (22.8%)

412 (14.7%)

42 (15.1%)

1,095 (18.6%)

10-14

518 (18.5%)

383 (13.7%)

40 (14.4%)

941 (16.0%)

15-19

359 (12.8%)

364 (13.0%)

20 (7.2%)

743 (12.6%)

20-29

468 (16.7%)

575 (20.5%)

30 (10.8%)

1,073 (18.2%)

30-49

179 (6.4%)

557 (19.9%)

18 (6.5%)

754 (12.8%)

50-69

2 (0.1%)

91 (3.2%)

2 (0.7%)

95 (1.6%)

70+

0 (0.0%)

5 (0.2%)

1 (0.4%)

6 (0.1%)

0 (0.0%)

0 (0.0%)

86 (30.9%)

86 (1.5%)

@@ -1582,7 +1664,7 @@

Other tips

  • Include the argument na.rm = TRUE to exclude missing values from any of the above calculations.
  • -
  • If applying any adorn_*() helper functions to tables not created by tabyl(), you can specify particular column(s) to apply them to like adorn_percentage(,,,c(cases,deaths)) (specify them to the 4th unnamed argument). The syntax is not simple. Consider using summarise() instead.
    +
  • If applying any adorn_*() helper functions to tables not created by tabyl(), you can specify particular column(s) to apply them to like. adorn_percentage(,,,c(cases,deaths)) (specify them to the 4th unnamed argument). The syntax is not simple. Consider using summarise() instead.
  • You can read more detail in the janitor page and this tabyl vignette.
@@ -1627,11 +1709,11 @@

Get counts

The above command can be shortened by using the count() function instead. count() does the following:

    -
  1. Groups the data by the columns provided to it
    +
  2. Groups the data by the columns provided to it.
  3. -
  4. Summarises them with n() (creating column n)
    +
  5. Summarises them with n() (creating column n).
  6. -
  7. Un-groups the data
  8. +
  9. Un-groups the data.
linelist %>% 
@@ -1700,10 +1782,11 @@ 

Proportions

age_summary <- linelist %>% 
   count(age_cat) %>%                     # group and count by gender (produces "n" column)
   mutate(                                # create percent of column - note the denominator
-    percent = scales::percent(n / sum(n))) 
-
-# print
-age_summary
+ percent = scales::percent(n / sum(n)) + ) + +# print +age_summary
  age_cat    n percent
 1     0-4 1095  18.60%
@@ -1726,8 +1809,8 @@ 

Proportions

-
- +
+

@@ -1792,19 +1875,18 @@

Summary st

Some tips:

    -
  • Use sum() with a logic statement to “count” rows that meet certain criteria (==)
    +
  • Use sum() with a logic statement to “count” rows that meet certain criteria (==).
  • -
  • Note the use of na.rm = TRUE within mathematical functions like sum(), otherwise NA will be returned if there are any missing values
    +
  • Note the use of na.rm = TRUE within mathematical functions like sum(), otherwise NA will be returned if there are any missing values.
  • -
  • Use the function percent() from the scales package to easily convert to percents +
  • Use the function percent() from the scales package to easily convert to percents.
      -
    • Set accuracy = to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively
      +
    • Set accuracy = to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively.
  • -
  • Use round() from base R to specify decimals
    -
  • -
  • To calculate these statistics on the entire dataset, use summarise() without group_by()
    +
  • Use round() from base R to specify decimals.
  • +
  • To calculate these statistics on the entire dataset, use summarise() without group_by().
  • You may create columns for the purposes of later calculations (e.g. denominators) that you eventually drop from your data frame with select().
@@ -1877,13 +1959,6 @@

Percentiles

# get default percentile values of age (0%, 25%, 50%, 75%, 100%)
 linelist %>% 
   summarise(age_percentiles = quantile(age_years, na.rm = TRUE))
-
-
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
-dplyr 1.1.0.
-ℹ Please use `reframe()` instead.
-ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
-  always returns an ungrouped data frame and adjust accordingly.
-
  age_percentiles
 1               0
@@ -1892,21 +1967,14 @@ 

Percentiles

4 23 5 84
-
# get manually-specified percentile values of age (5%, 50%, 75%, 98%)
-linelist %>% 
-  summarise(
-    age_percentiles = quantile(
-      age_years,
-      probs = c(.05, 0.5, 0.75, 0.98), 
-      na.rm=TRUE)
-    )
-
-
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
-dplyr 1.1.0.
-ℹ Please use `reframe()` instead.
-ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
-  always returns an ungrouped data frame and adjust accordingly.
-
+
# get manually-specified percentile values of age (5%, 50%, 75%, 98%)
+linelist %>% 
+  summarise(
+    age_percentiles = quantile(
+      age_years,
+      probs = c(.05, 0.5, 0.75, 0.98), 
+      na.rm=TRUE)
+    )
  age_percentiles
 1               1
@@ -1917,15 +1985,15 @@ 

Percentiles

If you want to return quantiles by group, you may encounter long and less useful outputs if you simply add another column to group_by(). So, try this approach instead - create a column for each quantile level desired.

-
# get manually-specified percentile values of age (5%, 50%, 75%, 98%)
-linelist %>% 
-  group_by(hospital) %>% 
-  summarise(
-    p05 = quantile(age_years, probs = 0.05, na.rm=T),
-    p50 = quantile(age_years, probs = 0.5, na.rm=T),
-    p75 = quantile(age_years, probs = 0.75, na.rm=T),
-    p98 = quantile(age_years, probs = 0.98, na.rm=T)
-    )
+
# get manually-specified percentile values of age (5%, 50%, 75%, 98%)
+linelist %>% 
+  group_by(hospital) %>% 
+  summarise(
+    p05 = quantile(age_years, probs = 0.05, na.rm=T),
+    p50 = quantile(age_years, probs = 0.5, na.rm=T),
+    p75 = quantile(age_years, probs = 0.75, na.rm=T),
+    p98 = quantile(age_years, probs = 0.98, na.rm=T)
+    )
# A tibble: 6 × 5
   hospital                               p05   p50   p75   p98
@@ -1940,9 +2008,9 @@ 

Percentiles

While dplyr summarise() certainly offers more fine control, you may find that all the summary statistics you need can be produced with get_summary_stat() from the rstatix package. If operating on grouped data, if will return 0%, 25%, 50%, 75%, and 100%. If applied to ungrouped data, you can specify the percentiles with probs = c(.05, .5, .75, .98).

-
linelist %>% 
-  group_by(hospital) %>% 
-  rstatix::get_summary_stats(age, type = "quantile")
+
linelist %>% 
+  group_by(hospital) %>% 
+  rstatix::get_summary_stats(age, type = "quantile")
# A tibble: 6 × 8
   hospital                         variable     n  `0%` `25%` `50%` `75%` `100%`
@@ -1956,8 +2024,8 @@ 

Percentiles

-
linelist %>% 
-  rstatix::get_summary_stats(age, type = "quantile")
+
linelist %>% 
+  rstatix::get_summary_stats(age, type = "quantile")
# A tibble: 1 × 7
   variable     n  `0%` `25%` `50%` `75%` `100%`
@@ -1973,11 +2041,11 @@ 

Summa

For example, let’s say you are beginning with the data frame of counts below, called linelist_agg - it shows in “long” format the case counts by outcome and gender.

Below we create this example data frame of linelist case counts by outcome and gender (missing values removed for clarity).

-
linelist_agg <- linelist %>% 
-  drop_na(gender, outcome) %>% 
-  count(outcome, gender)
-
-linelist_agg
+
linelist_agg <- linelist %>% 
+  drop_na(gender, outcome) %>% 
+  count(outcome, gender)
+
+linelist_agg
  outcome gender    n
 1   Death      f 1227
@@ -1988,12 +2056,12 @@ 

Summa

To sum the counts (in column n) by group you can use summarise() but set the new column equal to sum(n, na.rm=T). To add a conditional element to the sum operation, you can use the subset bracket [ ] syntax on the counts column.

-
linelist_agg %>% 
-  group_by(outcome) %>% 
-  summarise(
-    total_cases  = sum(n, na.rm=T),
-    male_cases   = sum(n[gender == "m"], na.rm=T),
-    female_cases = sum(n[gender == "f"], na.rm=T))
+
linelist_agg %>% 
+  group_by(outcome) %>% 
+  summarise(
+    total_cases  = sum(n, na.rm=T),
+    male_cases   = sum(n[gender == "m"], na.rm=T),
+    female_cases = sum(n[gender == "f"], na.rm=T))
# A tibble: 2 × 4
   outcome total_cases male_cases female_cases
@@ -2007,32 +2075,18 @@ 

Summa

across() multiple columns

You can use summarise() across multiple columns using across(). This makes life easier when you want to calculate the same statistics for many columns. Place across() within summarise() and specify the following:

    -
  • .cols = as either a vector of column names c() or “tidyselect” helper functions (explained below)
    +
  • .cols = as either a vector of column names c() or “tidyselect” helper functions (explained below).
  • -
  • .fns = the function to perform (no parentheses) - you can provide multiple within a list()
  • +
  • .fns = the function to perform (no parentheses) - you can provide multiple within a list().

Below, mean() is applied to several numeric columns. A vector of columns are named explicitly to .cols = and a single function mean is specified (no parentheses) to .fns =. Any additional arguments for the function (e.g. na.rm=TRUE) are provided after .fns =, separated by a comma.

It can be difficult to get the order of parentheses and commas correct when using across(). Remember that within across() you must include the columns, the functions, and any extra arguments needed for the functions.

-
linelist %>% 
-  group_by(outcome) %>% 
-  summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm),  # columns
-                   .fns = mean,                               # function
-                   na.rm=T))                                  # extra arguments
-
-
Warning: There was 1 warning in `summarise()`.
-ℹ In argument: `across(...)`.
-ℹ In group 1: `outcome = "Death"`.
-Caused by warning:
-! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
-Supply arguments directly to `.fns` through an anonymous function instead.
-
-  # Previously
-  across(a:b, mean, na.rm = TRUE)
-
-  # Now
-  across(a:b, \(x) mean(x, na.rm = TRUE))
-
+
linelist %>% 
+  group_by(outcome) %>% 
+  summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm),  # columns
+                   .fns = mean,                               # function
+                   na.rm=T))                                  # extra arguments
# A tibble: 3 × 5
   outcome age_years  temp wt_kg ht_cm
@@ -2044,11 +2098,11 @@ 

a

Multiple functions can be run at once. Below the functions mean and sd are provided to .fns = within a list(). You have the opportunity to provide character names (e.g. “mean” and “sd”) which are appended in the new column names.

-
linelist %>% 
-  group_by(outcome) %>% 
-  summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns
-                   .fns = list("mean" = mean, "sd" = sd),    # multiple functions 
-                   na.rm=T))                                 # extra arguments
+
linelist %>% 
+  group_by(outcome) %>% 
+  summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns
+                   .fns = list("mean" = mean, "sd" = sd),    # multiple functions 
+                   na.rm=T))                                 # extra arguments
# A tibble: 3 × 9
   outcome age_years_mean age_years_sd temp_mean temp_sd wt_kg_mean wt_kg_sd
@@ -2061,29 +2115,29 @@ 

a

Here are those “tidyselect” helper functions you can provide to .cols = to select columns:

    -
  • everything() - all other columns not mentioned
    +
  • everything() - all other columns not mentioned.
  • -
  • last_col() - the last column
    +
  • last_col() - the last column.
  • -
  • where() - applies a function to all columns and selects those which are TRUE
    +
  • where() - applies a function to all columns and selects those which are TRUE.
  • -
  • starts_with() - matches to a specified prefix. Example: starts_with("date")
  • -
  • ends_with() - matches to a specified suffix. Example: ends_with("_end")
    +
  • starts_with() - matches to a specified prefix. Example: starts_with("date").
  • +
  • ends_with() - matches to a specified suffix. Example: ends_with("_end").
  • -
  • contains() - columns containing a character string. Example: contains("time")
  • -
  • matches() - to apply a regular expression (regex). Example: contains("[pt]al")
    +
  • contains() - columns containing a character string. Example: contains("time").
  • +
  • matches() - to apply a regular expression (regex). Example: contains("[pt]al").
  • -
  • num_range() -
  • -
  • any_of() - matches if column is named. Useful if the name might not exist. Example: any_of(date_onset, date_death, cardiac_arrest)
  • +
  • num_range() - matches a numerical range. Example: num_range("wk", 1:5) would select columns with the prefix “wk” and the number 1:5 after.
  • +
  • any_of() - matches if column is named. Useful if the name might not exist. Example: any_of(date_onset, date_death, cardiac_arrest).

For example, to return the mean of every numeric column use where() and provide the function as.numeric() (without parentheses). All this remains within the across() command.

-
linelist %>% 
-  group_by(outcome) %>% 
-  summarise(across(
-    .cols = where(is.numeric),  # all numeric columns in the data frame
-    .fns = mean,
-    na.rm=T))
+
linelist %>% 
+  group_by(outcome) %>% 
+  summarise(across(
+    .cols = where(is.numeric),  # all numeric columns in the data frame
+    .fns = mean,
+    na.rm=T))
# A tibble: 3 × 12
   outcome generation   age age_years   lon   lat wt_kg ht_cm ct_blood  temp
@@ -2100,22 +2154,22 @@ 

Pivot widerIf you prefer your table in “wide” format you can transform it using the tidyr pivot_wider() function. You will likely need to re-name the columns with rename(). For more information see the page on Pivoting data.

The example below begins with the “long” table age_by_outcome from the proportions section. We create it again and print, for clarity:

-
age_by_outcome <- linelist %>%                  # begin with linelist
-  group_by(outcome) %>%                         # group by outcome 
-  count(age_cat) %>%                            # group and count by age_cat, and then remove age_cat grouping
-  mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group
+
age_by_outcome <- linelist %>%                  # begin with linelist
+  group_by(outcome) %>%                         # group by outcome 
+  count(age_cat) %>%                            # group and count by age_cat, and then remove age_cat grouping
+  mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group
-
- +
+

To pivot wider, we create the new columns from the values in the existing column age_cat (by setting names_from = age_cat). We also specify that the new table values will come from the existing column n, with values_from = n. The columns not mentioned in our pivoting command (outcome) will remain unchanged on the far left side.

-
age_by_outcome %>% 
-  select(-percent) %>%   # keep only counts for simplicity
-  pivot_wider(names_from = age_cat, values_from = n)  
+
age_by_outcome %>% 
+  select(-percent) %>%   # keep only counts for simplicity
+  pivot_wider(names_from = age_cat, values_from = n)  
# A tibble: 3 × 10
 # Groups:   outcome [3]
@@ -2135,17 +2189,17 @@ 

j

If your table consists only of counts or proportions/percents that can be summed into a total, then you can add sum totals using janitor’s adorn_totals() as described in the section above. Note that this function can only sum the numeric columns - if you want to calculate other total summary statistics see the next approach with dplyr.

Below, linelist is grouped by gender and summarised into a table that described the number of cases with known outcome, deaths, and recovered. Piping the table to adorn_totals() adds a total row at the bottom reflecting the sum of each column. The further adorn_*() functions adjust the display as noted in the code.

-
linelist %>% 
-  group_by(gender) %>%
-  summarise(
-    known_outcome = sum(!is.na(outcome)),           # Number of rows in group where outcome is not missing
-    n_death  = sum(outcome == "Death", na.rm=T),    # Number of rows in group where outcome is Death
-    n_recover = sum(outcome == "Recover", na.rm=T), # Number of rows in group where outcome is Recovered
-  ) %>% 
-  adorn_totals() %>%                                # Adorn total row (sums of each numeric column)
-  adorn_percentages("col") %>%                      # Get column proportions
-  adorn_pct_formatting() %>%                        # Convert proportions to percents
-  adorn_ns(position = "front")                      # display % and counts (with counts in front)
+
linelist %>% 
+  group_by(gender) %>%
+  summarise(
+    known_outcome = sum(!is.na(outcome)),           # Number of rows in group where outcome is not missing
+    n_death  = sum(outcome == "Death", na.rm=T),    # Number of rows in group where outcome is Death
+    n_recover = sum(outcome == "Recover", na.rm=T), # Number of rows in group where outcome is Recovered
+  ) %>% 
+  adorn_totals() %>%                                # Adorn total row (sums of each numeric column)
+  adorn_percentages("col") %>%                      # Get column proportions
+  adorn_pct_formatting() %>%                        # Convert proportions to percents
+  adorn_ns(position = "front")                      # display % and counts (with counts in front)
 gender  known_outcome        n_death      n_recover
       f 2,180  (47.8%) 1,227  (47.5%)   953  (48.1%)
@@ -2160,14 +2214,15 @@ 

Joining data page. Below is an example:

You can make a summary table of outcome by hospital with group_by() and summarise() like this:

-
by_hospital <- linelist %>% 
-  filter(!is.na(outcome) & hospital != "Missing") %>%  # Remove cases with missing outcome or hospital
-  group_by(hospital, outcome) %>%                      # Group data
-  summarise(                                           # Create new summary columns of indicators of interest
-    N = n(),                                            # Number of rows per hospital-outcome group     
-    ct_value = median(ct_blood, na.rm=T))               # median CT value per group
-  
-by_hospital # print table
+
by_hospital <- linelist %>% 
+  filter(!is.na(outcome) & hospital != "Missing") %>%  # Remove cases with missing outcome or hospital
+  group_by(hospital, outcome) %>%                      # Group data
+  summarise(                                           # Create new summary columns of indicators of interest
+    N = n(),                                           # Number of rows per hospital-outcome group     
+    ct_value = median(ct_blood, na.rm=T)               # median CT value per group
+    )               
+  
+by_hospital # print table
# A tibble: 10 × 4
 # Groups:   hospital [5]
@@ -2187,14 +2242,14 @@ 

-
totals <- linelist %>% 
-      filter(!is.na(outcome) & hospital != "Missing") %>%
-      group_by(outcome) %>%                            # Grouped only by outcome, not by hospital    
-      summarise(
-        N = n(),                                       # These statistics are now by outcome only     
-        ct_value = median(ct_blood, na.rm=T))
-
-totals # print table
+
totals <- linelist %>% 
+      filter(!is.na(outcome) & hospital != "Missing") %>%
+      group_by(outcome) %>%                            # Grouped only by outcome, not by hospital    
+      summarise(
+        N = n(),                                       # These statistics are now by outcome only     
+        ct_value = median(ct_blood, na.rm=T))
+
+totals # print table
# A tibble: 2 × 3
   outcome     N ct_value
@@ -2205,35 +2260,35 @@ 

Cleaning data and core functions page).

-
table_long <- bind_rows(by_hospital, totals) %>% 
-  mutate(hospital = replace_na(hospital, "Total"))
+
table_long <- bind_rows(by_hospital, totals) %>% 
+  mutate(hospital = replace_na(hospital, "Total"))

Here is the new table with “Total” rows at the bottom.

-
- +
+

This table is in a “long” format, which may be what you want. Optionally, you can pivot this table wider to make it more readable. See the section on pivoting wider above, and the Pivoting data page. You can also add more columns, and arrange it nicely. This code is below.

-
table_long %>% 
-  
-  # Pivot wider and format
-  ########################
-  mutate(hospital = replace_na(hospital, "Total")) %>% 
-  pivot_wider(                                         # Pivot from long to wide
-    values_from = c(ct_value, N),                       # new values are from ct and count columns
-    names_from = outcome) %>%                           # new column names are from outcomes
-  mutate(                                              # Add new columns
-    N_Known = N_Death + N_Recover,                               # number with known outcome
-    Pct_Death = scales::percent(N_Death / N_Known, 0.1),         # percent cases who died (to 1 decimal)
-    Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)
-  select(                                              # Re-order columns
-    hospital, N_Known,                                   # Intro columns
-    N_Recover, Pct_Recover, ct_value_Recover,            # Recovered columns
-    N_Death, Pct_Death, ct_value_Death)  %>%             # Death columns
-  arrange(N_Known)                                  # Arrange rows from lowest to highest (Total row at bottom)
+
table_long %>% 
+  
+  # Pivot wider and format
+  ########################
+  mutate(hospital = replace_na(hospital, "Total")) %>% 
+  pivot_wider(                                         # Pivot from long to wide
+    values_from = c(ct_value, N),                       # new values are from ct and count columns
+    names_from = outcome) %>%                           # new column names are from outcomes
+  mutate(                                              # Add new columns
+    N_Known = N_Death + N_Recover,                               # number with known outcome
+    Pct_Death = scales::percent(N_Death / N_Known, 0.1),         # percent cases who died (to 1 decimal)
+    Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)
+  select(                                              # Re-order columns
+    hospital, N_Known,                                   # Intro columns
+    N_Recover, Pct_Recover, ct_value_Recover,            # Recovered columns
+    N_Death, Pct_Death, ct_value_Death)  %>%             # Death columns
+  arrange(N_Known)                                  # Arrange rows from lowest to highest (Total row at bottom)
# A tibble: 6 × 8
 # Groups:   hospital [6]
@@ -2251,7 +2306,7 @@ 

Tables for presentation page.

-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -2260,34 +2315,33 @@

17.5 gtsummary package

If you want to print your summary statistics in a pretty, publication-ready graphic, you can use the gtsummary package and its function tbl_summary(). The code can seem complex at first, but the outputs look very nice and print to your RStudio Viewer panel as an HTML image. Read a vignette here.

-

You can also add the results of statistical tests to gtsummary tables. This process is described in the gtsummary section of the Simple statistical tests page.

+

You can also add the results of statistical tests to gtsummary tables. This process is described in the gtsummary section of the Simple statistical tests page.

To introduce tbl_summary() we will show the most basic behavior first, which actually produces a large and beautiful table. Then, we will examine in detail how to make adjustments and more tailored tables.

Summary table

The default behavior of tbl_summary() is quite incredible - it takes the columns you provide and creates a summary table in one command. The function prints statistics appropriate to the column class: median and inter-quartile range (IQR) for numeric columns, and counts (%) for categorical columns. Missing values are converted to “Unknown”. Footnotes are added to the bottom to explain the statistics, while the total N is shown at the top.

-
linelist %>% 
-  select(age_years, gender, outcome, fever, temp, hospital) %>%  # keep only the columns of interest
-  tbl_summary()                                                  # default
+
linelist %>% 
+  select(age_years, gender, outcome, fever, temp, hospital) %>%  # keep only the columns of interest
+  tbl_summary()                                                  # default
-
-
- - +
- - + + @@ -2813,7 +2879,8 @@

Summary table

- + @@ -2822,7 +2889,6 @@

Summary table

-

Adjustments

@@ -2838,29 +2904,28 @@

Adjustments

A simple example of a statistic = equation might look like below, to only print the mean of column age_years:

-
linelist %>% 
-  select(age_years) %>%         # keep only columns of interest 
-  tbl_summary(                  # create summary table
-    statistic = age_years ~ "{mean}") # print mean of age
+
linelist %>% 
+  select(age_years) %>%         # keep only columns of interest 
+  tbl_summary(                  # create summary table
+    statistic = age_years ~ "{mean}") # print mean of age
-
-
- -
CharacteristicN = 5,8881

Characteristic

N = 5,888

+1
1 Median (IQR); n (%)1 +

Median (Q1, Q3); n (%)

+
++++ - - + + @@ -3303,7 +3384,8 @@

Adjustments

- + @@ -3312,32 +3394,30 @@

Adjustments

-

A slightly more complex equation might look like "({min}, {max})", incorporating the max and min values within parentheses and separated by a comma:

-
linelist %>% 
-  select(age_years) %>%                       # keep only columns of interest 
-  tbl_summary(                                # create summary table
-    statistic = age_years ~ "({min}, {max})") # print min and max of age
+
linelist %>% 
+  select(age_years) %>%                       # keep only columns of interest 
+  tbl_summary(                                # create summary table
+    statistic = age_years ~ "({min}, {max})") # print min and max of age
-
-
- -
CharacteristicN = 5,8881

Characteristic

N = 5,888

+1
1 Mean1 +

Mean

+
++++ - - + + @@ -3780,7 +3876,8 @@

Adjustments

- + @@ -3789,7 +3886,6 @@

Adjustments

-

You can also differentiate syntax for separate columns or types of columns. In the more complex example below, the value provided to statistc = is a list indicating that for all continuous columns the table should print mean with standard deviation in parentheses, while for all categorical columns it should print the n, denominator, and percent.

digits =
Adjust the digits and rounding. Optionally, this can be specified to be for continuous columns only (as below).

@@ -3800,50 +3896,47 @@

Adjustments

type =
This is used to adjust how many levels of the statistics are shown. The syntax is similar to statistic = in that you provide an equation with columns on the left and a value on the right. Two common scenarios include:

    -
  • type = all_categorical() ~ "categorical" Forces dichotomous columns (e.g. fever yes/no) to show all levels instead of only the “yes” row
    -
  • -
  • type = all_continuous() ~ "continuous2" Allows multi-line statistics per variable, as shown in a later section
  • +
  • type = all_categorical() ~ "categorical" Forces dichotomous columns (e.g. fever yes/no) to show all levels instead of only the “yes” row.
  • +
  • type = all_continuous() ~ "continuous2" Allows multi-line statistics per variable, as shown in a later section.

In the example below, each of these arguments is used to modify the original summary table:

-
linelist %>% 
-  select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only columns of interest
-  tbl_summary(     
-    by = outcome,                                               # stratify entire table by outcome
-    statistic = list(all_continuous() ~ "{mean} ({sd})",        # stats and format for continuous columns
-                     all_categorical() ~ "{n} / {N} ({p}%)"),   # stats and format for categorical columns
-    digits = all_continuous() ~ 1,                              # rounding for continuous columns
-    type   = all_categorical() ~ "categorical",                 # force all categorical levels to display
-    label  = list(                                              # display labels for column names
-      outcome   ~ "Outcome",                           
-      age_years ~ "Age (years)",
-      gender    ~ "Gender",
-      temp      ~ "Temperature",
-      hospital  ~ "Hospital"),
-    missing_text = "Missing"                                    # how missing values should display
-  )
+
linelist %>% 
+  select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only columns of interest
+  tbl_summary(     
+    by = outcome,                                               # stratify entire table by outcome
+    statistic = list(all_continuous() ~ "{mean} ({sd})",        # stats and format for continuous columns
+                     all_categorical() ~ "{n} / {N} ({p}%)"),   # stats and format for categorical columns
+    digits = all_continuous() ~ 1,                              # rounding for continuous columns
+    type   = all_categorical() ~ "categorical",                 # force all categorical levels to display
+    label  = list(                                              # display labels for column names
+      age_years ~ "Age (years)",
+      gender    ~ "Gender",
+      temp      ~ "Temperature",
+      hospital  ~ "Hospital"),
+    missing_text = "Missing"                                    # how missing values should display
+  )
-
1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.
+
1323 missing rows in the "outcome" column have been removed.
-
-
- -
CharacteristicN = 5,8881

Characteristic

N = 5,888

+1
1 (Range)1 +

(Min, Max)

+
@@ -4276,9 +4378,18 @@

Adjustments

- - - + + + @@ -4301,13 +4412,13 @@

Adjustments

- - + + - - + + @@ -4323,13 +4434,13 @@

Adjustments

- - + + - - + + @@ -4355,37 +4466,38 @@

Adjustments

- - + + - - + + - - + + - - + + - - + + - - + + - + @@ -4394,40 +4506,38 @@

Adjustments

-

Multi-line stats for continuous variables

If you want to print multiple lines of statistics for continuous variables, you can indicate this by setting the type = to “continuous2”. You can combine all of the previously shown elements in one table by choosing which statistics you want to show. To do this you need to tell the function that you want to get a table back by entering the type as “continuous2”. The number of missing values is shown as “Unknown”.

-
linelist %>% 
-  select(age_years, temp) %>%                      # keep only columns of interest
-  tbl_summary(                                     # create summary table
-    type = all_continuous() ~ "continuous2",       # indicate that you want to print multiple statistics 
-    statistic = all_continuous() ~ c(
-      "{mean} ({sd})",                             # line 1: mean and SD
-      "{median} ({p25}, {p75})",                   # line 2: median and IQR
-      "{min}, {max}")                              # line 3: min and max
-    )
+
linelist %>% 
+  select(age_years, temp) %>%                      # keep only columns of interest
+  tbl_summary(                                     # create summary table
+    type = all_continuous() ~ "continuous2",       # indicate that you want to print multiple statistics 
+    statistic = all_continuous() ~ c(
+      "{mean} ({sd})",                             # line 1: mean and SD
+      "{median} ({p25}, {p75})",                   # line 2: median and IQR
+      "{min}, {max}")                              # line 3: min and max
+    )
-
-
- -
CharacteristicDeath, N = 2,5821Recover, N = 1,9831

Characteristic

Death
+N = 2,582

+1

Recover
+N = 1,983

+1
    f1,227 / 2,455 (50%)953 / 1,903 (50%)1,227 / 2455 (50%)953 / 1903 (50%)
    m1,228 / 2,455 (50%)950 / 1,903 (50%)1,228 / 2455 (50%)950 / 1903 (50%)
    Missing
    no458 / 2,460 (19%)361 / 1,904 (19%)458 / 2460 (19%)361 / 1904 (19%)
    yes2,002 / 2,460 (81%)1,543 / 1,904 (81%)2,002 / 2460 (81%)1,543 / 1904 (81%)
    Missing
    Central Hospital193 / 2,582 (7.5%)165 / 1,983 (8.3%)193 / 2582 (7.5%)165 / 1983 (8.3%)
    Military Hospital399 / 2,582 (15%)309 / 1,983 (16%)399 / 2582 (15%)309 / 1983 (16%)
    Missing611 / 2,582 (24%)514 / 1,983 (26%)611 / 2582 (24%)514 / 1983 (26%)
    Other395 / 2,582 (15%)290 / 1,983 (15%)395 / 2582 (15%)290 / 1983 (15%)
    Port Hospital785 / 2,582 (30%)579 / 1,983 (29%)785 / 2582 (30%)579 / 1983 (29%)
    St. Mark's Maternity Hospital (SMMH)199 / 2,582 (7.7%)126 / 1,983 (6.4%)199 / 2582 (7.7%)126 / 1983 (6.4%)
1 Mean (SD); n / N (%)1 +

Mean (SD); n / N (%)

+
- - + + @@ -4874,11 +4995,11 @@

16 (13)

- + - + @@ -4895,11 +5016,11 @@

38.56 (0.98)

- + - + @@ -4912,134 +5033,624 @@

statistical tests.

- - -
-

17.6 base R

-

You can use the function table() to tabulate and cross-tabulate columns. Unlike the options above, you must specify the dataframe each time you reference a column name, as shown below.

-

CAUTION: NA (missing) values will not be tabulated unless you include the argument useNA = "always" (which could also be set to “no” or “ifany”).

-

TIP: You can use the %$% from magrittr to remove the need for repeating data frame calls within base functions. For example the below could be written linelist %$% table(outcome, useNA = "always")

-
-
table(linelist$outcome, useNA = "always")
-
-

-  Death Recover    <NA> 
-   2582    1983    1323 
-
-
-

Multiple columns can be cross-tabulated by listing them one after the other, separated by commas. Optionally, you can assign each column a “name” like Outcome = linelist$outcome.

-
-
age_by_outcome <- table(linelist$age_cat, linelist$outcome, useNA = "always") # save table as object
-age_by_outcome   # print table
-
-
       
-        Death Recover <NA>
-  0-4     471     364  260
-  5-9     476     391  228
-  10-14   438     303  200
-  15-19   323     251  169
-  20-29   477     367  229
-  30-49   329     238  187
-  50-69    33      38   24
-  70+       3       3    0
-  <NA>     32      28   26
-
-
-
-

Proportions

-

To return proportions, passing the above table to the function prop.table(). Use the margins = argument to specify whether you want the proportions to be of rows (1), of columns (2), or of the whole table (3). For clarity, we pipe the table to the round() function from base R, specifying 2 digits.

-
-
# get proportions of table defined above, by rows, rounded
-prop.table(age_by_outcome, 1) %>% round(2)
-
-
       
-        Death Recover <NA>
-  0-4    0.43    0.33 0.24
-  5-9    0.43    0.36 0.21
-  10-14  0.47    0.32 0.21
-  15-19  0.43    0.34 0.23
-  20-29  0.44    0.34 0.21
-  30-49  0.44    0.32 0.25
-  50-69  0.35    0.40 0.25
-  70+    0.50    0.50 0.00
-  <NA>   0.37    0.33 0.30
-
-
-
-
-

Totals

-

To add row and column totals, pass the table to addmargins(). This works for both counts and proportions.

-
-
addmargins(age_by_outcome)
-
-
       
-        Death Recover <NA>  Sum
-  0-4     471     364  260 1095
-  5-9     476     391  228 1095
-  10-14   438     303  200  941
-  15-19   323     251  169  743
-  20-29   477     367  229 1073
-  30-49   329     238  187  754
-  50-69    33      38   24   95
-  70+       3       3    0    6
-  <NA>     32      28   26   86
-  Sum    2582    1983 1323 5888
-
-
-
-

Convert to data frame

-

Converting a table() object directly to a data frame is not straight-forward. One approach is demonstrated below:

-
    -
  1. Create the table, without using useNA = "always". Instead convert NA values to “(Missing)” with fct_explicit_na() from forcats.
    -
  2. -
  3. Add totals (optional) by piping to addmargins()
    -
  4. -
  5. Pipe to the base R function as.data.frame.matrix()
    -
  6. -
  7. Pipe the table to the tibble function rownames_to_column(), specifying the name for the first column
    -
  8. -
  9. Print, View, or export as desired. In this example we use flextable() from package flextable as described in the Tables for presentation page. This will print to the RStudio viewer pane as a pretty HTML image.
  10. -
+
+

17.5.1 tbl_wide_summary()

+

You may also want to display your results in wide format, rather than long. To do so in gtsummary you can use the function tbl_wide_summary().

-
table(fct_explicit_na(linelist$age_cat), fct_explicit_na(linelist$outcome)) %>% 
-  addmargins() %>% 
-  as.data.frame.matrix() %>% 
-  tibble::rownames_to_column(var = "Age Category") %>% 
-  flextable::flextable()
+
linelist %>% 
+     select(age_years, temp) %>%
+     tbl_wide_summary()
-

CharacteristicN = 5,888

Characteristic

N = 5,888

    Median (IQR)    Median (Q1, Q3) 13 (6, 23)
    Range    Min, Max 0, 84
    Median (IQR)    Median (Q1, Q3) 38.80 (38.20, 39.20)
    Range    Min, Max 35.20, 40.80

Age Category

Death

Recover

(Missing)

Sum

0-4

471

364

260

1,095

5-9

476

391

228

1,095

10-14

438

303

200

941

15-19

323

251

169

743

20-29

477

367

229

1,073

30-49

329

238

187

754

50-69

33

38

24

95

70+

3

3

0

6

(Missing)

32

28

26

86

Sum

2,582

1,983

1,323

5,888

-
-
- -
- -
-

17.7 Resources

-

Much of the information in this page is adapted from these resources and vignettes online:

-

gtsummary

-

dplyr

+
+ + + +++++ + + + + + + + + + + + + + + + + + + + +

Characteristic

Median

Q1, Q3

age_years136, 23
temp38.8038.20, 39.20
+ +
+
+
+

There are many other ways to modify these tables, including adding p-values, adjusting color and headings, etc. Many of these are described in the documentation (enter ?tbl_summary in Console), and some are given in the section on statistical tests.

+ + +
+

17.6 base R

+

You can use the function table() to tabulate and cross-tabulate columns. Unlike the options above, you must specify the dataframe each time you reference a column name, as shown below.

+

CAUTION: NA (missing) values will not be tabulated unless you include the argument useNA = "always" (which could also be set to “no” or “ifany”).

+

TIP: You can use the %$% from magrittr to remove the need for repeating data frame calls within base functions. For example the below could be written linelist %$% table(outcome, useNA = "always")

+
+
table(linelist$outcome, useNA = "always")
+
+

+  Death Recover    <NA> 
+   2582    1983    1323 
+
+
+

Multiple columns can be cross-tabulated by listing them one after the other, separated by commas. Optionally, you can assign each column a “name” like Outcome = linelist$outcome.

+
+
age_by_outcome <- table(linelist$age_cat, linelist$outcome, useNA = "always") # save table as object
+age_by_outcome   # print table
+
+
       
+        Death Recover <NA>
+  0-4     471     364  260
+  5-9     476     391  228
+  10-14   438     303  200
+  15-19   323     251  169
+  20-29   477     367  229
+  30-49   329     238  187
+  50-69    33      38   24
+  70+       3       3    0
+  <NA>     32      28   26
+
+
+
+

Proportions

+

To return proportions, passing the above table to the function prop.table(). Use the margins = argument to specify whether you want the proportions to be of rows (1), of columns (2), or of the whole table (3). For clarity, we pipe the table to the round() function from base R, specifying 2 digits.

+
+
# get proportions of table defined above, by rows, rounded
+prop.table(age_by_outcome, 1) %>% round(2)
+
+
       
+        Death Recover <NA>
+  0-4    0.43    0.33 0.24
+  5-9    0.43    0.36 0.21
+  10-14  0.47    0.32 0.21
+  15-19  0.43    0.34 0.23
+  20-29  0.44    0.34 0.21
+  30-49  0.44    0.32 0.25
+  50-69  0.35    0.40 0.25
+  70+    0.50    0.50 0.00
+  <NA>   0.37    0.33 0.30
+
+
+
+
+

Totals

+

To add row and column totals, pass the table to addmargins(). This works for both counts and proportions.

+
+
addmargins(age_by_outcome)
+
+
       
+        Death Recover <NA>  Sum
+  0-4     471     364  260 1095
+  5-9     476     391  228 1095
+  10-14   438     303  200  941
+  15-19   323     251  169  743
+  20-29   477     367  229 1073
+  30-49   329     238  187  754
+  50-69    33      38   24   95
+  70+       3       3    0    6
+  <NA>     32      28   26   86
+  Sum    2582    1983 1323 5888
+
+
+
+
+

Convert to data frame

+

Converting a table() object directly to a data frame is not straight-forward. One approach is demonstrated below:

+
    +
  1. Create the table, without using useNA = "always". Instead convert NA values to “(Missing)” with fct_explicit_na() from forcats.
    +
  2. +
  3. Add totals (optional) by piping to addmargins().
    +
  4. +
  5. Pipe to the base R function as.data.frame.matrix().
    +
  6. +
  7. Pipe the table to the tibble function rownames_to_column(), specifying the name for the first column.
  8. +
  9. Print, View, or export as desired. In this example we use flextable() from package flextable as described in the Tables for presentation page. This will print to the RStudio viewer pane as a pretty HTML image.
  10. +
+
+
table(fct_explicit_na(linelist$age_cat), fct_explicit_na(linelist$outcome)) %>% 
+  addmargins() %>% 
+  as.data.frame.matrix() %>% 
+  tibble::rownames_to_column(var = "Age Category") %>% 
+  flextable::flextable()
+
+

Age Category

Death

Recover

(Missing)

Sum

0-4

471

364

260

1,095

5-9

476

391

228

1,095

10-14

438

303

200

941

15-19

323

251

169

743

20-29

477

367

229

1,073

30-49

329

238

187

754

50-69

33

38

24

95

70+

3

3

0

6

(Missing)

32

28

26

86

Sum

2,582

1,983

1,323

5,888

+
+
+ +
+
+
+

17.7 Resources

+

Much of the information in this page is adapted from these resources and vignettes online:

+

gtsummary

+

dplyr

+ + +
+ + + diff --git a/html_outputs/new_pages/tables_presentation.html b/html_outputs/new_pages/tables_presentation.html index d012570a..e2937c47 100644 --- a/html_outputs/new_pages/tables_presentation.html +++ b/html_outputs/new_pages/tables_presentation.html @@ -2,12 +2,12 @@ - + -The Epidemiologist R Handbook - 29  Tables for presentation +29  Tables for presentation – The Epidemiologist R Handbook

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

This page demonstrates how to convert summary data frames into presentation-ready tables with the flextable package. These tables can be inserted into powerpoint slides, HTML pages, PDF or Word documents, etc.

@@ -741,12 +822,13 @@

Load packages

here, # file pathways flextable, # make HTML tables officer, # helper functions for tables - tidyverse) # data management, summary, and visualization

+ tidyverse # data management, summary, and visualization + )

Import data

-

To begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).

+

To begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).

# import the linelist
 linelist <- import("linelist_cleaned.rds")
@@ -754,8 +836,8 @@

Import data

The first 50 rows of the linelist are displayed below.

-
- +
+
@@ -827,7 +909,7 @@

Create a fle
my_table <- flextable(table) 
 my_table
-

hospital

N_Known

N_Recover

Pct_Recover

ct_value_Recover

N_Death

Pct_Death

ct_value_Death

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

hospital

N_Known

N_Recover

Pct_Recover

ct_value_Recover

N_Death

Pct_Death

ct_value_Death

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

After doing this, we can progressively pipe the my_table object through more flextable formatting functions.

@@ -851,7 +933,7 @@

Column width

my_table %>% autofit()
-

hospital

N_Known

N_Recover

Pct_Recover

ct_value_Recover

N_Death

Pct_Death

ct_value_Death

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

hospital

N_Known

N_Recover

Pct_Recover

ct_value_Recover

N_Death

Pct_Death

ct_value_Death

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

However, this might not always be appropriate, especially if there are very long values within cells, meaning the table might not fit on the page.

@@ -864,7 +946,7 @@

Column width

my_table
-

hospital

N_Known

N_Recover

Pct_Recover

ct_value_Recover

N_Death

Pct_Death

ct_value_Death

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

hospital

N_Known

N_Recover

Pct_Recover

ct_value_Recover

N_Death

Pct_Death

ct_value_Death

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -903,13 +985,13 @@

Column headers my_table # print

-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

Borders and background

-

You can adjust the borders, internal lines, etc. with various flextable functions. It is often easier to start by removing all existing borders with border_remove().

+

You can adjust the borders, internal lines, etc., with various flextable functions. It is often easier to start by removing all existing borders with border_remove().

Then, you can apply default border themes by passing the table to theme_box(), theme_booktabs(), or theme_alafoli().

You can add vertical and horizontal lines with a variety of functions. hline() and vline() add lines to a specified row or column, respectively. Within each, you must specify the part = as either “all”, “body”, or “header”. For vertical lines, specify the column to j =, and for horizontal lines the row to i =. Other functions like vline_right(), vline_left(), hline_top(), and hline_bottom() add lines to the outsides only.

In all of these functions, the actual line style itself must be specified to border = and must be the output of a separate command using the fp_border() function from the officer package. This function helps you define the width and color of the line. You can define this above the table commands, as shown below.

@@ -932,7 +1014,7 @@

Borders my_table

-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -944,7 +1026,7 @@

Font and ali flextable::align(align = "center", j = c(2:8), part = "all") my_table
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

Additionally, we can increase the header font size and change then to bold. We can also change the total row to bold.

@@ -956,15 +1038,15 @@

Font and ali my_table
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

We can ensure that the proportion columns display only one decimal place using the function colformat_num(). Note this could also have been done at data management stage with the round() function.

-
my_table <- colformat_num(my_table, j = c(4,7), digits = 1)
+
my_table <- colformat_num(my_table, j = c(4, 7), digits = 1)
 my_table
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -978,7 +1060,7 @@

Merge cells

my_table
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -991,7 +1073,7 @@

Background col my_table
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -1002,9 +1084,9 @@

We can highlight all values in a column that meet a certain rule, e.g. where more than 55% of cases died. Simply put the criteria to the i = or j = argument, preceded by a tilde ~. Reference the column in the data frame, not the display heading values.

my_table %>% 
-  bg(j = 7, i = ~ Pct_Death >= 55, part = "body", bg = "red") 
+ bg(j = 7, i = ~ Pct_Death > 55, part = "body", bg = "red")
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

Or, we can highlight the entire row meeting a certain criterion, such as a hospital of interest. To do this we just remove the column (j) specification so the criteria apply to all columns.

@@ -1012,7 +1094,7 @@

my_table %>% 
   bg(., i= ~ hospital == "Military Hospital", part = "body", bg = "#91c293") 
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -1109,7 +1191,7 @@

table
-

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

+

Hospital

Total cases with known outcome

Recovered

Died

Total

% of cases

Median CT values

Total

% of cases

Median CT values

St. Mark's Maternity Hospital (SMMH)

325

126

38.8%

22

199

61.2%

22

Central Hospital

358

165

46.1%

22

193

53.9%

22

Other

685

290

42.3%

21

395

57.7%

22

Military Hospital

708

309

43.6%

22

399

56.4%

21

Missing

1,125

514

45.7%

21

611

54.3%

21

Port Hospital

1,364

579

42.4%

21

785

57.6%

22

Total

3,440

1,469

42.7%

22

1,971

57.3%

22

@@ -1152,9 +1234,10 @@

Print

29.6 Resources

-

The full flextable book is here: https://ardata-fr.github.io/flextable-book/ The Github site is here
-A manual of all the flextable functions can be found here

-

A gallery of beautiful example flextable tables with code can be accessed here

+

The full flextable book is here.

+

The Github site is here.

+

A manual of all the flextable functions can be found here.

+

A gallery of beautiful example flextable tables with code can be accessed here.

@@ -1339,18 +1422,7 @@

- + -The Epidemiologist R Handbook - 23  Time series and outbreak detection +23  Time series and outbreak detection – The Epidemiologist R Handbook

Indicateurs pour la province de: Shanghai

Indicateurs

Estimation

Mean delay onset-hosp

4.0

Percentage of recovery

46.7

Median age of the cases

67.0

+

Indicateurs pour la province de: Shanghai

Indicateurs

Estimation

Mean delay onset-hosp

4.0

Percentage of recovery

46.7

Median age of the cases

67.0

+ +
print_indic_prov(table_indic_all, "Jiangsu")
+
+
Warning in set_formatter_type(tab_print, fmt_double = "%.2f", na_str = "-"):
+Use `colformat_*()` instead.
-
print_indic_prov(table_indic_all, "Jiangsu")
-

Indicateurs pour la province de: Jiangsu

Indicateurs

Estimation

Mean delay onset-hosp

6.0

Percentage of recovery

71.4

Median age of the cases

55.0

+

Indicateurs pour la province de: Jiangsu

Indicateurs

Estimation

Mean delay onset-hosp

6.0

Percentage of recovery

71.4

Median age of the cases

55.0

-
-

44.8 Tips and best Practices for well functioning functions

+
+

45.8 Tips and best Practices for well functioning functions

Functional programming is meant to ease code and facilitates its reading. It should produce the contrary. The tips below will help you having a clean code and easy to read code.

Naming and syntax

    -
  • Avoid using character that could have been easily already taken by other functions already existing in your environment

  • -
  • It is recommended for the function name to be short and straightforward to understand for another reader

  • +
  • Avoid using character that could have been easily already taken by other functions already existing in your environment.

  • +
  • It is recommended for the function name to be short and straightforward to understand for another reader.

  • It is preferred to use verbs as the function name and nouns for the argument names.

@@ -1220,13 +1349,13 @@

tidyverse programming guidance. Among the topics covered are tidy evaluation and use of the embrace { } “double braces”

For example, here is a quick skeleton template code from page tutorial mentioned just above:

-
var_summary <- function(data, var) {
-  data %>%
-    summarise(n = n(), min = min({{ var }}), max = max({{ var }}))
-}
-mtcars %>% 
-  group_by(cyl) %>% 
-  var_summary(mpg)
+
var_summary <- function(data, var) {
+  data %>%
+    summarise(n = n(), min = min({{ var }}), max = max({{ var }}))
+}
+mtcars %>% 
+  group_by(cyl) %>% 
+  var_summary(mpg)

@@ -1236,22 +1365,22 @@

Test
  • It can be more than recommended to introduce a check on the missingness of one argument using missing(argument). This simple check can return “TRUE” or “FALSE” value.
  • -
    contain_covid19_missing <- function(barrier_gest, wear_mask, get_vaccine){
    -  
    -  if (missing(barrier_gest)) (print("please provide arg1"))
    -  if (missing(wear_mask)) print("please provide arg2")
    -  if (missing(get_vaccine)) print("please provide arg3")
    -
    -
    -  if (!barrier_gest == "yes" | wear_mask =="yes" | get_vaccine == "yes" ) 
    -       
    -       return ("you can do better")
    -  
    -  else("please make sure all are yes, this pandemic has to end!")
    -}
    -
    -
    -contain_covid19_missing(get_vaccine = "yes")
    +
    contain_covid19_missing <- function(barrier_gest, wear_mask, get_vaccine){
    +  
    +  if (missing(barrier_gest)) (print("please provide arg1"))
    +  if (missing(wear_mask)) print("please provide arg2")
    +  if (missing(get_vaccine)) print("please provide arg3")
    +
    +
    +  if (!barrier_gest == "yes" | wear_mask =="yes" | get_vaccine == "yes" ) 
    +       
    +       return ("you can do better")
    +  
    +  else("please make sure all are yes, this pandemic has to end!")
    +}
    +
    +
    +contain_covid19_missing(get_vaccine = "yes")
    [1] "please provide arg1"
     [1] "please provide arg2"
    @@ -1264,19 +1393,19 @@

    Test
  • Use stop() for more detectable errors.
  • -
    contain_covid19_stop <- function(barrier_gest, wear_mask, get_vaccine){
    -  
    -  if(!is.character(barrier_gest)) (stop("arg1 should be a character, please enter the value with `yes`, `no` or `sometimes"))
    -  
    -  if (barrier_gest == "yes" & wear_mask =="yes" & get_vaccine == "yes" ) 
    -       
    -       return ("success")
    -  
    -  else("please make sure all are yes, this pandemic has to end!")
    -}
    -
    -
    -contain_covid19_stop(barrier_gest=1, wear_mask="yes", get_vaccine = "no")
    +
    contain_covid19_stop <- function(barrier_gest, wear_mask, get_vaccine){
    +  
    +  if(!is.character(barrier_gest)) (stop("arg1 should be a character, please enter the value with `yes`, `no` or `sometimes"))
    +  
    +  if (barrier_gest == "yes" & wear_mask =="yes" & get_vaccine == "yes" ) 
    +       
    +       return ("success")
    +  
    +  else("please make sure all are yes, this pandemic has to end!")
    +}
    +
    +
    +contain_covid19_stop(barrier_gest=1, wear_mask="yes", get_vaccine = "no")
    Error in contain_covid19_stop(barrier_gest = 1, wear_mask = "yes", get_vaccine = "no"): arg1 should be a character, please enter the value with `yes`, `no` or `sometimes
    @@ -1287,7 +1416,7 @@

    Test

    We can verify by first running the mean() as function, then run it with safely().

    -
    map(linelist, mean)
    +
    map(linelist, mean)
    $case_id
     [1] NA
    @@ -1381,9 +1510,9 @@ 

    Test

    -
    safe_mean <- safely(mean)
    -linelist %>% 
    -  map(safe_mean)
    +
    safe_mean <- safely(mean)
    +linelist %>% 
    +  map(safe_mean)
    $case_id
     $case_id$result
    @@ -1629,8 +1758,8 @@ 

    Test

    -
    -

    44.9 Resources

    +
    +

    45.9 Resources

    R for Data Science link

    Cheatsheet advance R programming

    Cheatsheet purr Package

    @@ -1819,18 +1948,7 @@

    diff --git a/html_outputs/pt/new_pages/data_used.pt.html b/html_outputs/pt/new_pages/data_used.pt.html index 3bc0d3ac..a423fedc 100644 --- a/html_outputs/pt/new_pages/data_used.pt.html +++ b/html_outputs/pt/new_pages/data_used.pt.html @@ -711,7 +711,7 @@

    Existem duas maneiras de baixar o livro:

    diff --git a/html_outputs/ru/new_pages/data_used.ru.html b/html_outputs/ru/new_pages/data_used.ru.html index 70aac7a6..6a72ca33 100644 --- a/html_outputs/ru/new_pages/data_used.ru.html +++ b/html_outputs/ru/new_pages/data_used.ru.html @@ -710,7 +710,7 @@

    Использовать ссылку для скачивания

    -

    Ждя быстрого доступа правый щелчок по этой ссылке и выберите “Save link as - сохранить ссылку как”.

    +

    Ждя быстрого доступа правый щелчок по этой ссылке и выберите “Save link as - сохранить ссылку как”.

    Если вы работаете на компьютере Mac, используйте Cmd+щелчок. Если вы используете мобильный телефон, нажмите и удерживайте ссылку, затем выберите “Сохранить ссылку”. Руководство будет загружено на ваше устройство. Если появится окно с необработанным HTML-кодом, убедитесь, что вы выполнили приведенные выше инструкции, или попробуйте вариант 2.

    diff --git a/html_outputs/search.json b/html_outputs/search.json index 92584c69..ba04f3c8 100644 --- a/html_outputs/search.json +++ b/html_outputs/search.json @@ -65,7 +65,7 @@ "href": "new_pages/editorial_style.html#approach-and-style", "title": "1  Editorial and technical notes", "section": "", - "text": "This is a code reference book accompanied by relatively brief examples - not a thorough textbook on R or data science\n\nThis is a R handbook for use within applied epidemiology - not a manual on the methods or science of applied epidemiology\n\nThis is intended to be a living document - optimal R packages for a given task change often and we welcome discussion about which to emphasize in this handbook\n\n\nR packages\nSo many choices\nOne of the most challenging aspects of learning R is knowing which R package to use for a given task. It is a common occurrence to struggle through a task only later to realize - hey, there’s an R package that does all that in one command line!\nIn this handbook, we try to offer you at least two ways to complete each task: one tried-and-true method (probably in base R or tidyverse) and one special R package that is custom-built for that purpose. We want you to have a couple options in case you can’t download a given package or it otherwise does not work for you.\nIn choosing which packages to use, we prioritized R packages and approaches that have been tested and vetted by the community, minimize the number of packages used in a typical work session, that are stable (not changing very often), and that accomplish the task simply and cleanly\nThis handbook generally prioritizes R packages and functions from the tidyverse. Tidyverse is a collection of R packages designed for data science that share underlying grammar and data structures. All tidyverse packages can be installed or loaded via the tidyverse package. Read more at the tidyverse website.\nWhen applicable, we also offer code options using base R - the packages and functions that come with R at installation. This is because we recognize that some of this book’s audience may not have reliable internet to download extra packages.\nLinking functions to packages explicitly\nIt is often frustrating in R tutorials when a function is shown in code, but you don’t know which package it is from! We try to avoid this situation.\nIn the narrative text, package names are written in bold (e.g. dplyr) and functions are written like this: mutate(). We strive to be explicit about which package a function comes from, either by referencing the package in nearby text or by specifying the package explicitly in the code like this: dplyr::mutate(). It may look redundant, but we are doing it on purpose.\nSee the page on R basics to learn more about packages and functions.\n\n\nCode style\nIn the handbook, we frequently utilize “new lines”, making our code appear “long”. We do this for a few reasons:\n\nWe can write explanatory comments with # that are adjacent to each little part of the code\n\nGenerally, longer (vertical) code is easier to read\n\nIt is easier to read on a narrow screen (no sideways scrolling needed)\n\nFrom the indentations, it can be easier to know which arguments belong to which function\n\nAs a result, code that could be written like this:\n\nlinelist %>% \n group_by(hospital) %>% # group rows by hospital\n slice_max(date, n = 1, with_ties = F) # if there's a tie (of date), take the first row\n\n…is written like this:\n\nlinelist %>% \n group_by(hospital) %>% # group rows by hospital\n slice_max(\n date, # keep row per group with maximum date value \n n = 1, # keep only the single highest row \n with_ties = F) # if there's a tie (of date), take the first row\n\nR code is generally not affected by new lines or indentations. When writing code, if you initiate a new line after a comma it will apply automatic indentation patterns.\nWe also use lots of spaces (e.g. n = 1 instead of n=1) because it is easier to read. Be kind to the people reading your code!\n\n\nNomenclature\nIn this handbook, we generally reference “columns” and “rows” instead of “variables” and “observations”. As explained in this primer on “tidy data”, most epidemiological statistical datasets consist structurally of rows, columns, and values.\nVariables contain the values that measure the same underlying attribute (like age group, outcome, or date of onset). Observations contain all values measured on the same unit (e.g. a person, site, or lab sample). So these aspects can be more difficult to tangibly define.\nIn “tidy” datasets, each column is a variable, each row is an observation, and each cell is a single value. However some datasets you encounter will not fit this mold - a “wide” format dataset may have a variable split across several columns (see an example in the Pivoting data page). Likewise, observations could be split across several rows.\nMost of this handbook is about managing and transforming data, so referring to the concrete data structures of rows and columns is more relevant than the more abstract observations and variables. Exceptions occur primarily in pages on data analysis, where you will see more references to variables and observations.\n\n\nNotes\nHere are the types of notes you may encounter in the handbook:\nNOTE: This is a note\nTIP: This is a tip.\nCAUTION: This is a cautionary note.\nDANGER: This is a warning.", + "text": "This is a code reference book accompanied by relatively brief examples - not a thorough textbook on R or data science.\nThis is a R handbook for use within applied epidemiology - not a manual on the methods or science of applied epidemiology.\nThis is intended to be a living document - optimal R packages for a given task change often and we welcome discussion about which to emphasize in this handbook.\n\n\nR packages\nSo many choices\nOne of the most challenging aspects of learning R is knowing which R package to use for a given task. It is a common occurrence to struggle through a task only later to realize - hey, there’s an R package that does all that in one command line!\nIn this handbook, we try to offer you at least two ways to complete each task: one tried-and-true method (probably in base R or tidyverse) and one special R package that is custom-built for that purpose. We want you to have a couple of options in case you can’t download a given package or it otherwise does not work for you.\nIn choosing which packages to use, we prioritized R packages and approaches that have been tested and vetted by the community, minimized the number of packages used in a typical work session, focused on packages that are stable (not changing very often), and that accomplish the task simply and cleanly.\nThis handbook generally prioritizes R packages and functions from the tidyverse. Tidyverse is a collection of R packages designed for data science that share underlying grammar and data structures. All tidyverse packages can be installed or loaded via the tidyverse package. Read more at the tidyverse website.\nWhen applicable, we also offer code options using base R - the packages and functions that come with R at installation. This is because we recognize that some of this book’s audience may not have reliable internet to download extra packages.\nLinking functions to packages explicitly\nIt is often frustrating in R tutorials when a function is shown in code, but you don’t know which package it is from! We try to avoid this situation.\nIn the narrative text, package names are written in bold (e.g. dplyr) and functions are written like this: mutate(). We strive to be explicit about which package a function comes from, either by referencing the package in nearby text or by specifying the package explicitly in the code like this: dplyr::mutate(). It may look redundant, but we are doing it on purpose.\nSee the page on R basics to learn more about packages and functions.\n\n\nCode style\nIn the handbook, we frequently utilize “new lines”, making our code appear “long”. We do this for a few reasons:\n\nWe can write explanatory comments with # that are adjacent to each little part of the code.\n\nGenerally, longer (vertical) code is easier to read.\n\nIt is easier to read on a narrow screen (no sideways scrolling needed).\n\nFrom the indentations, it can be easier to know which arguments belong to which function.\n\nAs a result, code that could be written like this:\n\nlinelist %>% \n group_by(hospital) %>% # group rows by hospital\n slice_max(date, n = 1, with_ties = F) # if there's a tie (of date), take the first row\n\n…is written like this:\n\nlinelist %>% \n group_by(hospital) %>% # group rows by hospital\n slice_max(\n date, # keep row per group with maximum date value \n n = 1, # keep only the single highest row \n with_ties = F) # if there's a tie (of date), take the first row\n\nR code is generally not affected by new lines or indentations. When writing code, if you initiate a new line after a comma it will apply automatic indentation patterns.\nWe also use lots of spaces (e.g. n = 1 instead of n=1) because it is easier to read. Be kind to the people reading your code!\n\n\nNomenclature\nIn this handbook, we generally reference “columns” and “rows” instead of “variables” and “observations”. As explained in this primer on “tidy data”, most epidemiological statistical datasets consist structurally of rows, columns, and values.\nVariables contain the values that measure the same underlying attribute (like age group, outcome, or date of onset). Observations contain all values measured on the same unit (e.g. a person, site, or lab sample). These aspects can be more difficult to tangibly define.\nIn “tidy” datasets, each column is a variable, each row is an observation, and each cell is a single value. However some datasets you encounter will not fit this mold - a “wide” format dataset may have a variable split across several columns (see an example in the Pivoting data page). Likewise, observations could be split across several rows.\nMost of this handbook is about managing and transforming data, so referring to the concrete data structures of rows and columns is more relevant than the more abstract observations and variables. Exceptions occur primarily in pages on data analysis, where you will see more references to variables and observations.\n\n\nNotes\nHere are the types of notes you may encounter in the handbook:\nNOTE: This is a note\nTIP: This is a tip.\nCAUTION: This is a cautionary note.\nDANGER: This is a warning.", "crumbs": [ "About this book", "1  Editorial and technical notes" @@ -87,7 +87,7 @@ "href": "new_pages/editorial_style.html#major-revisions", "title": "1  Editorial and technical notes", "section": "1.3 Major revisions", - "text": "1.3 Major revisions\n\n\n\nDate\nMajor changes\n\n\n\n\n10 May 2021\nRelease of version 1.0.0\n\n\n20 Nov 2022\nRelease of version 1.0.1\n\n\n\nNEWS With version 1.0.1 the following changes have been implemented:\n\nUpdate to R version 4.2\n\nData cleaning: switched {linelist} to {matchmaker}, removed unnecessary line from case_when() example\n\nDates: switched {linelist} guess_date() to {parsedate} parse_date()\nPivoting: slight update to pivot_wider() id_cols=\n\nSurvey analysis: switched plot_age_pyramid() to age_pyramid(), slight change to alluvial plot code\n\nHeat plots: added ungroup() to agg_weeks chunk\n\nInteractive plots: added ungroup() to chunk that makes agg_weeks so that expand() works as intended\n\nTime series: added data.frame() around objects within all trending::fit() and predict() commands\n\nCombinations analysis: Switch case_when() to ifelse() and added optional across() code for preparing the data\n\nTransmission chains: Update to more recent version of {epicontacts}", + "text": "1.3 Major revisions\n\n\n\nDate\nMajor changes\n\n\n\n\n10 May 2021\nRelease of version 1.0.0\n\n\n20 Nov 2022\nRelease of version 1.0.1\n\n\n\nNEWS With version 1.0.1 the following changes have been implemented:\n\nUpdate to R version 4.2.\n\nData cleaning: switched {linelist} to {matchmaker}, removed unnecessary line from case_when() example.\n\nDates: switched linelist guess_date() to parsedate parse_date().\nPivoting: slight update to pivot_wider() id_cols=.\n\nSurvey analysis: switched plot_age_pyramid() to age_pyramid(), slight change to alluvial plot code.\n\nHeat plots: added ungroup() to agg_weeks chunk.\n\nInteractive plots: added ungroup() to chunk that makes agg_weeks so that expand() works as intended.\n\nTime series: added data.frame() around objects within all trending::fit() and predict() commands.\n\nCombinations analysis: Switch case_when() to ifelse() and added optional across() code for preparing the data.\n\nTransmission chains: Update to more recent version of epicontacts.", "crumbs": [ "About this book", "1  Editorial and technical notes" @@ -98,7 +98,7 @@ "href": "new_pages/editorial_style.html#session-info-r-rstudio-packages", "title": "1  Editorial and technical notes", "section": "1.4 Session info (R, RStudio, packages)", - "text": "1.4 Session info (R, RStudio, packages)\nBelow is the information on the versions of R, RStudio, and R packages used during this rendering of the Handbook.\n\nsessioninfo::session_info()\n\n─ Session info ───────────────────────────────────────────────────────────────\n setting value\n version R version 4.3.2 (2023-10-31 ucrt)\n os Windows 11 x64 (build 22621)\n system x86_64, mingw32\n ui RTerm\n language (EN)\n collate English_United States.utf8\n ctype English_United States.utf8\n tz Europe/Stockholm\n date 2024-06-19\n pandoc 3.1.11 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)\n\n─ Packages ───────────────────────────────────────────────────────────────────\n package * version date (UTC) lib source\n cli 3.6.2 2023-12-11 [2] CRAN (R 4.3.2)\n digest 0.6.35 2024-03-11 [1] CRAN (R 4.3.3)\n evaluate 0.23 2023-11-01 [2] CRAN (R 4.3.2)\n fastmap 1.1.1 2023-02-24 [2] CRAN (R 4.3.2)\n htmltools 0.5.8 2024-03-25 [1] CRAN (R 4.3.3)\n htmlwidgets 1.6.4 2023-12-06 [2] CRAN (R 4.3.2)\n jsonlite 1.8.8 2023-12-04 [2] CRAN (R 4.3.2)\n knitr 1.45 2023-10-30 [2] CRAN (R 4.3.2)\n rlang 1.1.3 2024-01-10 [2] CRAN (R 4.3.2)\n rmarkdown 2.26 2024-03-05 [1] CRAN (R 4.3.3)\n rstudioapi 0.15.0 2023-07-07 [2] CRAN (R 4.3.2)\n sessioninfo 1.2.2 2021-12-06 [2] CRAN (R 4.3.2)\n xfun 0.43 2024-03-25 [1] CRAN (R 4.3.3)\n\n [1] C:/Users/ngulu864/AppData/Local/R/win-library/4.3\n [2] C:/Program Files/R/R-4.3.2/library\n\n──────────────────────────────────────────────────────────────────────────────", + "text": "1.4 Session info (R, RStudio, packages)\nBelow is the information on the versions of R, RStudio, and R packages used during this rendering of the Handbook.\n\nsessioninfo::session_info()\n\n─ Session info ───────────────────────────────────────────────────────────────\n setting value\n version R version 4.4.1 (2024-06-14 ucrt)\n os Windows 10 x64 (build 19045)\n system x86_64, mingw32\n ui RTerm\n language (EN)\n collate English_United Kingdom.utf8\n ctype English_United Kingdom.utf8\n tz America/Los_Angeles\n date 2024-09-30\n pandoc 3.2 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)\n\n─ Packages ───────────────────────────────────────────────────────────────────\n package * version date (UTC) lib source\n cli 3.6.3 2024-06-21 [1] CRAN (R 4.4.1)\n digest 0.6.37 2024-08-19 [1] CRAN (R 4.4.1)\n evaluate 1.0.0 2024-09-17 [1] CRAN (R 4.4.1)\n fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.4.1)\n htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.4.1)\n htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.4.1)\n jsonlite 1.8.9 2024-09-20 [1] CRAN (R 4.4.1)\n knitr 1.48 2024-07-07 [1] CRAN (R 4.4.1)\n rlang 1.1.4 2024-06-04 [1] CRAN (R 4.4.1)\n rmarkdown 2.28 2024-08-17 [1] CRAN (R 4.4.1)\n rstudioapi 0.16.0 2024-03-24 [1] CRAN (R 4.4.1)\n sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.4.1)\n xfun 0.47 2024-08-17 [1] CRAN (R 4.4.1)\n\n [1] C:/Program Files/R/R-4.4.1/library\n\n──────────────────────────────────────────────────────────────────────────────", "crumbs": [ "About this book", "1  Editorial and technical notes" @@ -120,7 +120,7 @@ "href": "new_pages/data_used.html#download-offline-handbook", "title": "2  Download handbook and data", "section": "", - "text": "When you open the file it may take a minute or two for the images and the Table of Contents to load\n\nThe offline handbook has a slightly different layout - one very long page with Table of Contents on the left. To search for specific terms use Ctrl+f (Cmd-f)\n\nSee the Suggested packages page to assist you with installing appropriate R packages before you lose internet connectivity\n\nInstall our R package epirhandbook that contains all the example data (install process described below)\n\n\n\nUse download link\nFor quick access, right-click this link and select “Save link as”.\nIf on a Mac, use Cmd+click. If on a mobile, press and hold the link and select “Save link”. The handbook will download to your device. If a screen with raw HTML code appears, ensure you have followed the above instructions or try Option 2.\n\n\nUse our R package\nWe offer an R package called epirhandbook. It includes a function download_book() that downloads the handbook file from our Github repository to your computer.\nThis package also contains a function get_data() that downloads all the example data to your computer.\nRun the following code to install our R package epirhandbook from the Github repository appliedepi. This package is not on CRAN, so use the special function p_install_gh() to install it from Github.\n\n# install the latest version of the Epi R Handbook package\npacman::p_install_gh(\"appliedepi/epirhandbook\")\n\nNow, load the package for use in your current R session:\n\n# load the package for use\npacman::p_load(epirhandbook)\n\nNext, run the package’s function download_book() (with empty parentheses) to download the handbook to your computer. Assuming you are in RStudio, a window will appear allowing you to select a save location.\n\n# download the offline handbook to your computer\ndownload_book()", + "text": "When you open the file it may take a minute or two for the images and the ‘Table of Contents’ to load.\n\nThe offline handbook has a slightly different layout - one very long page with Table of Contents on the left. To search for specific terms use Ctrl+f (Cmd-f).\n\nSee the Suggested packages page to assist you with installing appropriate R packages before you lose internet connectivity. You will not be able to install new packages without internet access.\n\nInstall our R package epirhandbook that contains all the example data (install process described below).\n\n\n\nOption 1: Use download link\nFor quick access, right-click this link and select “Save link as”.\nIf on a Mac, use Cmd+click. If on a mobile, press and hold the link and select “Save link”. The handbook will download to your device. If a screen with raw HTML code appears, ensure you have followed the above instructions or try Option 2.\n\n\nOption 2: Use our R package\nWe offer an R package called epirhandbook. It includes a function download_book() that downloads the handbook file from our Github repository to your computer. If you wish to download the book in a language other than English, you can specify this by specifying the language.\n\ndownload_book(fr)\n\nCurrently we support French (fr), German (de), Spanish (es), Portuguese (pt), Vietnamese (vn), Japanese (jp), Turkish (tr), and Russian (ru).\nThis package also contains a function get_data() that downloads all the example data to your computer.\nRun the following code to install our R package epirhandbook from the Github repository appliedepi. This package is not on CRAN, so use the special function p_install_gh() to install it from Github.\n\n# install the latest version of the Epi R Handbook package\npacman::p_install_gh(\"appliedepi/epirhandbook\")\n\nNow, load the package for use in your current R session:\n\n# load the package for use\npacman::p_load(epirhandbook)\n\nNext, run the package’s function download_book() (with empty parentheses) to download the handbook to your computer. Assuming you are in RStudio, a window will appear allowing you to select a save location.\n\n# download the offline handbook to your computer\ndownload_book()", "crumbs": [ "About this book", "2  Download handbook and data" @@ -131,7 +131,7 @@ "href": "new_pages/data_used.html#download-data-to-follow-along", "title": "2  Download handbook and data", "section": "2.2 Download data to follow along", - "text": "2.2 Download data to follow along\nTo “follow along” with the handbook pages, you can download the example data and outputs.\n\nUse our R package\nThe easiest approach to download all the data is to install our R package epirhandbook. It contains a function get_data() that saves all the example data to a folder of your choice on your computer.\nTo install our R package epirhandbook, run the following code. This package is not on CRAN, so use the function p_install_gh() to install it. The input is referencing our Github organisation (“appliedepi”) and the epirhandbook package.\n\n# install the latest version of the Epi R Handbook package\npacman::p_install_gh(\"appliedepi/epirhandbook\")\n\nNow, load the package for use in your current R session:\n\n# load the package for use\npacman::p_load(epirhandbook)\n\nNext, use the package’s function get_data() to download the example data to your computer. Run get_data(\"all\") to get all the example data, or provide a specific file name and extension within the quotes to retrieve only one file.\nThe data have already been downloaded with the package, and simply need to be transferred out to a folder on your computer. A pop-up window will appear, allowing you to select a save folder location. We suggest you create a new “data” folder as there are about 30 files (including example data and example outputs).\n\n# download all the example data into a folder on your computer\nget_data(\"all\")\n\n# download only the linelist example data into a folder on your computer\nget_data(file = \"linelist_cleaned.rds\")\n\n\n# download a specific file into a folder on your computer\nget_data(\"linelist_cleaned.rds\")\n\nOnce you have used get_data() to save a file to your computer, you will still need to import it into R. see the Import and export page for details.\nIf you wish, you can review all the data used in this handbook in the “data” folder of our Github repository.\n\n\nDownload one-by-one\nThis option involves downloading the data file-by-file from our Github repository via either a link or an R command specific to the file. Some file types allow a download button, while others can be downloaded via an R command.\n\nCase linelist\nThis is a fictional Ebola outbreak, expanded by the handbook team from the ebola_sim practice dataset in the outbreaks package.\n\nClick to download the “raw” linelist (.xlsx). The “raw” case linelist is an Excel spreadsheet with messy data. Use this to follow-along with the Cleaning data and core functions page.\nClick to download the “clean” linelist (.rds). Use this file for all other pages of this handbook that use the linelist. A .rds file is an R-specific file type that preserves column classes. This ensures you will have only minimal cleaning to do after importing the data into R.\n\nOther related files:\n\nClick to download the “clean” linelist as an Excel file\nPart of the cleaning page uses a “cleaning dictionary” (.csv file). You can load it directly into R by running the following commands:\n\n\npacman::p_load(rio) # install/load the rio package\n\n# import the file directly from Github\ncleaning_dict <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/case_linelists/cleaning_dict.csv\")\n\n\n\nMalaria count data\nThese data are fictional counts of malaria cases by age group, facility, and day. A .rds file is an R-specific file type that preserves column classes. This ensures you will have only minimal cleaning to do after importing the data into R.\n Click to download the malaria count data (.rds file) \n\n\nLikert-scale data\nThese are fictional data from a Likert-style survey, used in the page on Demographic pyramids and Likert-scales. You can load these data directly into R by running the following commands:\n\npacman::p_load(rio) # install/load the rio package\n\n# import the file directly from Github\nlikert_data <- import(\"https://raw.githubusercontent.com/appliedepi/epirhandbook_eng/master/data/likert_data.csv\")\n\n\n\nFlexdashboard\nBelow are links to the file associated with the page on Dashboards with R Markdown:\n\nTo download the R Markdown for the outbreak dashboard, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\nTo download the HTML dashboard, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\n\n\nContact Tracing\nThe Contact Tracing page demonstrated analysis of contact tracing data, using example data from Go.Data. The data used in the page can be downloaded as .rds files by clicking the following links:\n Click to download the case investigation data (.rds file) \n Click to download the contact registration data (.rds file) \n Click to download the contact follow-up data (.rds file) \nNOTE: Structured contact tracing data from other software (e.g. KoBo, DHIS2 Tracker, CommCare) may look different. If you would like to contribute alternative sample data or content for this page, please contact us.\nTIP: If you are deploying Go.Data and want to connect to your instance’s API, see the Import and export page (API section) and the Go.Data Community of Practice.\n\n\nGIS\nShapefiles have many sub-component files, each with a different file extention. One file will have the “.shp” extension, but others may have “.dbf”, “.prj”, etc.\nThe GIS basics page provides links to the Humanitarian Data Exchange website where you can download the shapefiles directly as zipped files.\nFor example, the health facility points data can be downloaded here. Download “hotosm_sierra_leone_health_facilities_points_shp.zip”. Once saved to your computer, “unzip” the folder. You will see several files with different extensions (e.g. “.shp”, “.prj”, “.shx”) - all these must be saved to the same folder on your computer. Then to import into R, provide the file path and name of the “.shp” file to st_read() from the sf package (as described in the GIS basics page).\nIf you follow Option 1 to download all the example data (via our R package epirhandbook), all the shapefiles are included.\nAlternatively, you can download the shapefiles from the R Handbook Github “data” folder (see the “gis” sub-folder). However, be aware that you will need to download each sub-file individually to your computer. In Github, click on each file individually and download them by clicking on the “Download” button. Below, you can see how the shapefile “sle_adm3” consists of many files - each of which would need to be downloaded from Github.\n\n\n\n\n\n\n\n\n\n\n\nPhylogenetic trees\nSee the page on Phylogenetic trees. Newick file of phylogenetic tree constructed from whole genome sequencing of 299 Shigella sonnei samples and corresponding sample data (converted to a text file). The Belgian samples and resulting data are kindly provided by the Belgian NRC for Salmonella and Shigella in the scope of a project conducted by an ECDC EUPHEM Fellow, and will also be published in a manuscript. The international data are openly available on public databases (ncbi) and have been previously published.\n\nTo download the “Shigella_tree.txt” phylogenetic tree file, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\nTo download the “sample_data_Shigella_tree.csv” with additional information on each sample, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\nTo see the new, created subset-tree, right-click this link (Cmd+click for Mac) and select “Save link as”. The .txt file will download to your computer.\n\nYou can then import the .txt files with read.tree() from the ape package, as explained in the page.\n\nape::read.tree(\"Shigella_tree.txt\")\n\n\n\nStandardization\nSee the page on Standardised rates. You can load the data directly from our Github repository on the internet into your R session with the following commands:\n\n# install/load the rio package\npacman::p_load(rio) \n\n##############\n# Country A\n##############\n# import demographics for country A directly from Github\nA_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics.csv\")\n\n# import deaths for country A directly from Github\nA_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryA.csv\")\n\n##############\n# Country B\n##############\n# import demographics for country B directly from Github\nB_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics_2.csv\")\n\n# import deaths for country B directly from Github\nB_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryB.csv\")\n\n\n###############\n# Reference Pop\n###############\n# import demographics for country B directly from Github\nstandard_pop_data <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/world_standard_population_by_sex.csv\")\n\n\n\nTime series and outbreak detection\nSee the page on Time series and outbreak detection. We use campylobacter cases reported in Germany 2002-2011, as available from the surveillance R package. (nb. this dataset has been adapted from the original, in that 3 months of data have been deleted from the end of 2011 for demonstration purposes)\n Click to download Campylobacter in Germany (.xlsx) \nWe also use climate data from Germany 2002-2011 (temperature in degrees celsius and rain fail in millimetres) . These were downloaded from the EU Copernicus satellite reanalysis dataset using the ecmwfr package. You will need to download all of these and import them with stars::read_stars() as explained in the time series page.\n Click to download Germany weather 2002 (.nc file) \n Click to download Germany weather 2003 (.nc file) \n Click to download Germany weather 2004 (.nc file) \n Click to download Germany weather 2005 (.nc file) \n Click to download Germany weather 2006 (.nc file) \n Click to download Germany weather 2007 (.nc file) \n Click to download Germany weather 2008 (.nc file) \n Click to download Germany weather 2009 (.nc file) \n Click to download Germany weather 2010 (.nc file) \n Click to download Germany weather 2011 (.nc file) \n\n\nSurvey analysis\nFor the survey analysis page we use fictional mortality survey data based off MSF OCA survey templates. This fictional data was generated as part of the “R4Epis” project.\n Click to download Fictional survey data (.xlsx) \n Click to download Fictional survey data dictionary (.xlsx) \n Click to download Fictional survey population data (.xlsx) \n\n\nShiny\nThe page on Dashboards with Shiny demonstrates the construction of a simple app to display malaria data.\nTo download the R files that produce the Shiny app:\nYou can click here to download the app.R file that contains both the UI and Server code for the Shiny app.\nYou can click here to download the facility_count_data.rds file that contains malaria data for the Shiny app. Note that you may need to store it within a “data” folder for the here() file paths to work correctly.\nYou can click here to download the global.R file that should run prior to the app opening, as explained in the page.\nYou can click here to download the plot_epicurve.R file that is sourced by global.R. Note that you may need to store it within a “funcs” folder for the here() file paths to work correctly.", + "text": "2.2 Download data to follow along\nTo “follow along” with the handbook pages, you can download the example data and outputs.\n\nUse our R package\nThe easiest approach to download all the data is to install our R package epirhandbook. It contains a function get_data() that saves all the example data to a folder of your choice on your computer.\nTo install our R package epirhandbook, run the following code. This package is not on CRAN, so use the function p_install_gh() to install it. The input is referencing our Github organisation (“appliedepi”) and the epirhandbook package.\n\n# install the latest version of the Epi R Handbook package\npacman::p_install_gh(\"appliedepi/epirhandbook\")\n\nNow, load the package for use in your current R session:\n\n# load the package for use\npacman::p_load(epirhandbook)\n\nNext, use the package’s function get_data() to download the example data to your computer. Run get_data(\"all\") to get all the example data, or provide a specific file name and extension within the quotes to retrieve only one file.\nThe data have already been downloaded with the package, and simply need to be transferred out to a folder on your computer. A pop-up window will appear, allowing you to select a save folder location. We suggest you create a new “data” folder as there are almost 80 files (including example data and example outputs).\n\n# download all the example data into a folder on your computer\nget_data(\"all\")\n\n# download only the linelist example data into a folder on your computer\nget_data(file = \"linelist_cleaned.rds\")\n\n\n# download a specific file into a folder on your computer\nget_data(\"linelist_cleaned.rds\")\n\nOnce you have used get_data() to save a file to your computer, you will still need to import it into R. see the Import and export page for details.\nIf you wish, you can review all the data used in this handbook in the “data” folder of our Github repository.\n\n\nDownload one-by-one\nThis option involves downloading the data file-by-file from our Github repository via either a link or an R command specific to the file. Some file types allow a download button, while others can be downloaded via an R command.\n\nCase linelist\nThis is a fictional Ebola outbreak, expanded by the handbook team from the ebola_sim practice dataset in the outbreaks package.\n\nClick to download the “raw” linelist (.xlsx). The “raw” case linelist is an Excel spreadsheet with messy data. Use this to follow-along with the Cleaning data and core functions page.\nClick to download the “clean” linelist (.rds). Use this file for all other pages of this handbook that use the linelist. A .rds file is an R-specific file type that preserves column classes. This ensures you will have only minimal cleaning to do after importing the data into R.\n\nOther related files:\n\nClick to download the “clean” linelist as an Excel file\nPart of the cleaning page uses a “cleaning dictionary” (.csv file). You can load it directly into R by running the following commands:\n\n\npacman::p_load(rio) # install/load the rio package\n\n# import the file directly from Github\ncleaning_dict <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/case_linelists/cleaning_dict.csv\")\n\n\n\nMalaria count data\nThese data are fictional counts of malaria cases by age group, facility, and day. A .rds file is an R-specific file type that preserves column classes. This ensures you will have only minimal cleaning to do after importing the data into R.\n Click to download the malaria count data (.rds file) \n\n\nLikert-scale data\nThese are fictional data from a Likert-style survey, used in the page on Demographic pyramids and Likert-scales. You can load these data directly into R by running the following commands:\n\npacman::p_load(rio) # install/load the rio package\n\n# import the file directly from Github\nlikert_data <- import(\"https://raw.githubusercontent.com/appliedepi/epirhandbook_eng/master/data/likert_data.csv\")\n\n\n\nFlexdashboard\nBelow are links to the file associated with the page on Dashboards with R Markdown:\n\nTo download the R Markdown for the outbreak dashboard, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\nTo download the HTML dashboard, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\n\n\nContact Tracing\nThe Contact Tracing page demonstrated analysis of contact tracing data, using example data from Go.Data. The data used in the page can be downloaded as .rds files by clicking the following links:\n Click to download the case investigation data (.rds file) \n Click to download the contact registration data (.rds file) \n Click to download the contact follow-up data (.rds file) \n Click to download the contact follow-up data (.rds file) \nNOTE: Structured contact tracing data from other software (e.g. KoBo, DHIS2 Tracker, CommCare) may look different. If you would like to contribute alternative sample data or content for this page, please contact us.\nTIP: If you are deploying Go.Data and want to connect to your instance’s API, see the Import and export page (API section) and the Go.Data Community of Practice.\n\n\nGIS\nShapefiles have many sub-component files, each with a different file extention. One file will have the “.shp” extension, but others may have “.dbf”, “.prj”, etc. You will need to have all of these different files within the same folder to use the shapefile.\nThe GIS basics page provides links to the Humanitarian Data Exchange website where you can download the shapefiles directly as zipped files.\nFor example, the health facility points data can be downloaded here. Download “hotosm_sierra_leone_health_facilities_points_shp.zip”. Once saved to your computer, “unzip” the folder. You will see several files with different extensions (e.g. “.shp”, “.prj”, “.shx”) - all these must be saved to the same folder on your computer. Then to import into R, provide the file path and name of the “.shp” file to st_read() from the sf package (as described in the GIS basics page).\nIf you follow Option 1 to download all the example data (via our R package epirhandbook), all the shapefiles are included.\nAlternatively, you can download the shapefiles from the R Handbook Github “data” folder (see the “gis” sub-folder). However, be aware that you will need to download each sub-file individually to your computer. In Github, click on each file individually and download them by clicking on the “Download” button. Below, you can see how the shapefile “sle_adm3” consists of many files - each of which would need to be downloaded from Github.\n\n\n\n\n\n\n\n\n\n\n\nPhylogenetic trees\nSee the page on Phylogenetic trees. Newick file of phylogenetic tree constructed from whole genome sequencing of 299 Shigella sonnei samples and corresponding sample data (converted to a text file). The Belgian samples and resulting data are kindly provided by the Belgian NRC for Salmonella and Shigella in the scope of a project conducted by an ECDC EUPHEM Fellow, and are published here with the sequences found here. The international data are openly available on public databases (National Center for Biotechnology Information, NCBI) and have been previously published.\n\nTo download the “Shigella_tree.txt” phylogenetic tree file, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\nTo download the “sample_data_Shigella_tree.csv” with additional information on each sample, right-click this link (Cmd+click for Mac) and select “Save link as”.\n\nTo see the new, created subset-tree, right-click this link (Cmd+click for Mac) and select “Save link as”. The .txt file will download to your computer.\n\nYou can then import the .txt files with read.tree() from the ape package, as explained in the page.\n\nape::read.tree(\"Shigella_tree.txt\")\n\n\n\nStandardization\nSee the page on Standardised rates. You can load the data directly from our Github repository on the internet into your R session with the following commands:\n\n# install/load the rio package\npacman::p_load(rio) \n\n##############\n# Country A\n##############\n# import demographics for country A directly from Github\nA_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics.csv\")\n\n# import deaths for country A directly from Github\nA_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryA.csv\")\n\n##############\n# Country B\n##############\n# import demographics for country B directly from Github\nB_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics_2.csv\")\n\n# import deaths for country B directly from Github\nB_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryB.csv\")\n\n\n###############\n# Reference Pop\n###############\n# import demographics for country B directly from Github\nstandard_pop_data <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/world_standard_population_by_sex.csv\")\n\n\n\nTime series and outbreak detection\nSee the page on Time series and outbreak detection. We use campylobacter cases reported in Germany 2002-2011, as available from the surveillance R package. This dataset has been adapted from the original, in that 3 months of data have been deleted from the end of 2011 for demonstration purposes.\n Click to download Campylobacter in Germany (.xlsx) \nWe also use climate data from Germany 2002-2011 (temperature in degrees celsius and rain fail in millimetres) . These were downloaded from the EU Copernicus satellite reanalysis dataset using the ecmwfr package. You will need to download all of these and import them with stars::read_stars() as explained in the time series page.\n Click to download Germany weather 2002 (.nc file) \n Click to download Germany weather 2003 (.nc file) \n Click to download Germany weather 2004 (.nc file) \n Click to download Germany weather 2005 (.nc file) \n Click to download Germany weather 2006 (.nc file) \n Click to download Germany weather 2007 (.nc file) \n Click to download Germany weather 2008 (.nc file) \n Click to download Germany weather 2009 (.nc file) \n Click to download Germany weather 2010 (.nc file) \n Click to download Germany weather 2011 (.nc file) \n\n\nSurvey analysis\nFor the survey analysis page we use fictional mortality survey data based off MSF OCA survey templates. This fictional data was generated as part of the “R4Epis” project.\n Click to download Fictional survey data (.xlsx) \n Click to download Fictional survey data dictionary (.xlsx) \n Click to download Fictional survey population data (.xlsx) \n\n\nShiny\nThe page on Dashboards with Shiny demonstrates the construction of a simple app to display malaria data.\nTo download the R files that produce the Shiny app:\nYou can click here to download the app.R file that contains both the UI and Server code for the Shiny app.\nYou can click here to download the facility_count_data.rds file that contains malaria data for the Shiny app. Note that you may need to store it within a “data” folder for the here() file paths to work correctly.\nYou can click here to download the global.R file that should run prior to the app opening, as explained in the page.\nYou can click here to download the plot_epicurve.R file that is sourced by global.R. Note that you may need to store it within a “funcs” folder for the here() file paths to work correctly.", "crumbs": [ "About this book", "2  Download handbook and data" @@ -153,7 +153,7 @@ "href": "new_pages/basics.html#key-terms", "title": "3  R Basics", "section": "3.2 Key terms", - "text": "3.2 Key terms\nRStudio - RStudio is a Graphical User Interface (GUI) for easier use of R. Read more in the RStudio section.\nObjects - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are objects which are assigned a name and can be referenced in later commands. Read more in the Objects section.\nFunctions - A function is a code operation that accept inputs and returns a transformed output. Read more in the Functions section.\nPackages - An R package is a shareable bundle of functions. Read more in the Packages section.\nScripts - A script is the document file that hold your commands. Read more in the Scripts section", + "text": "3.2 Key terms\nRStudio - RStudio is a Graphical User Interface (GUI) for easier use of R. Read more in the RStudio section.\nObjects - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are objects which are assigned a name and can be referenced in later commands. Read more in the Objects section.\nFunctions - A function is a code operation that accept inputs and returns a transformed output. Read more in the Functions section.\nPackages - An R package is a shareable bundle of functions. Read more in the Packages section.\nScripts - A script is the document file that hold your commands. Read more in the Scripts section.", "crumbs": [ "Basics", "3  R Basics" @@ -164,7 +164,7 @@ "href": "new_pages/basics.html#learning", "title": "3  R Basics", "section": "3.3 Resources for learning", - "text": "3.3 Resources for learning\n\nResources within RStudio\nHelp documentation\nSearch the RStudio “Help” tab for documentation on R packages and specific functions. This is within the pane that also contains Files, Plots, and Packages (typically in the lower-right pane). As a shortcut, you can also type the name of a package or function into the R console after a question-mark to open the relevant Help page. Do not include parentheses.\nFor example: ?filter or ?diagrammeR.\nInteractive tutorials\nThere are several ways to learn R interactively within RStudio.\nRStudio itself offers a Tutorial pane that is powered by the learnr R package. Simply install this package and open a tutorial via the new “Tutorial” tab in the upper-right RStudio pane (which also contains Environment and History tabs).\nThe R package swirl offers interactive courses in the R Console. Install and load this package, then run the command swirl() (empty parentheses) in the R console. You will see prompts appear in the Console. Respond by typing in the Console. It will guide you through a course of your choice.\n\n\nCheatsheets\nThere are many PDF “cheatsheets” available on the RStudio website, for example:\n\nFactors with forcats package\n\nDates and times with lubridate package\n\nStrings with stringr package\n\niterative opertaions with purrr package\n\nData import\n\nData transformation cheatsheet with dplyr package\n\nR Markdown (to create documents like PDF, Word, Powerpoint…)\n\nShiny (to build interactive web apps)\n\nData visualization with ggplot2 package\n\nCartography (GIS)\n\nleaflet package (interactive maps)\n\nPython with R (reticulate package)\n\nThis is an online R resource specifically for Excel users\n\n\nTwitter\nR has a vibrant twitter community where you can learn tips, shortcuts, and news - follow these accounts:\n\nFollow us! @epiRhandbook\n\nR Function A Day @rfuntionaday is an incredible resource\n\nR for Data Science @rstats4ds\n\nRStudio @RStudio\n\nRStudio Tips @rstudiotips\n\nR-Bloggers @Rbloggers\n\nR-ladies @RLadiesGlobal\n\nHadley Wickham @hadleywickham\n\nAlso:\n#epitwitter and #rstats\n\n\nFree online resources\nA definitive text is the R for Data Science book by Garrett Grolemund and Hadley Wickham\nThe R4Epis project website aims to “develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting.” You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up.\n\n\nLanguages other than English\nMateriales de RStudio en Español\nIntroduction à R et au tidyverse (Francais)", + "text": "3.3 Resources for learning\n\nResources within RStudio\nHelp documentation\nSearch the RStudio “Help” tab for documentation on R packages and specific functions. This is within the pane that also contains Files, Plots, and Packages (typically in the lower-right pane). As a shortcut, you can also type the name of a package or function into the R console after a question-mark to open the relevant Help page. Do not include parentheses.\nFor example: ?filter or ?diagrammeR.\nInteractive tutorials\nThere are several ways to learn R interactively within RStudio.\nRStudio itself offers a Tutorial pane that is powered by the learnr R package. Simply install this package and open a tutorial via the new “Tutorial” tab in the upper-right RStudio pane (which also contains Environment and History tabs).\nThe R package swirl offers interactive courses in the R Console. Install and load this package, then run the command swirl() (empty parentheses) in the R console. You will see prompts appear in the Console. Respond by typing in the Console. It will guide you through a course of your choice.\n\n\nCheatsheets\nThere are many PDF “cheatsheets” available on the RStudio website, for example:\n\nFactors with forcats package.\n\nDates and times with lubridate package.\n\nStrings with stringr package.\n\niterative opertaions with purrr package.\n\nData import.\n\nData transformation cheatsheet with dplyr package.\n\nR Markdown (to create documents like PDF, Word, Powerpoint…).\n\nShiny (to build interactive web apps).\n\nData visualization with ggplot2 package.\n\nCartography (GIS).\n\nleaflet package (interactive maps).\n\nPython with R (reticulate package).\n\nThis is an online R resource specifically for Excel users.\n\n\nTwitter\n\nFollow us! @epiRhandbook\n\nAlso:\n#epitwitter and #rstats\n\n\nFree online resources\nA definitive text is the R for Data Science book by Garrett Grolemund and Hadley Wickham.\nThe R4Epis project website aims to “develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting”. You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up.\n\n\nLanguages other than English\nMateriales de RStudio en Español\nIntroduction à R et au tidyverse (Francais)", "crumbs": [ "Basics", "3  R Basics" @@ -175,7 +175,7 @@ "href": "new_pages/basics.html#installation", "title": "3  R Basics", "section": "3.4 Installation", - "text": "3.4 Installation\n\nR and RStudio\nHow to install R\nVisit this website https://www.r-project.org/ and download the latest version of R suitable for your computer.\nHow to install RStudio\nVisit this website https://rstudio.com/products/rstudio/download/ and download the latest free Desktop version of RStudio suitable for your computer.\nPermissions\nNote that you should install R and RStudio to a drive where you have read and write permissions. Otherwise, your ability to install R packages (a frequent occurrence) will be impacted. If you encounter problems, try opening RStudio by right-clicking the icon and selecting “Run as administrator”. Other tips can be found in the page R on network drives.\nHow to update R and RStudio\nYour version of R is printed to the R Console at start-up. You can also run sessionInfo().\nTo update R, go to the website mentioned above and re-install R. Alternatively, you can use the installr package (on Windows) by running installr::updateR(). This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the installr documentation.\nBe aware that the old R version will still exist in your computer. You can temporarily run an older version (older “installation”) of R by clicking “Tools” -> “Global Options” in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R.\nTo update RStudio, you can go to the website above and re-download RStudio. Another option is to click “Help” -> “Check for Updates” within RStudio, but this may not show the very latest updates.\nTo see which versions of R, RStudio, or packages were used when this Handbook as made, see the page on Editorial and technical notes.\n\n\nOther software you may need to install\n\nTinyTeX (for compiling an RMarkdown document to PDF)\n\nPandoc (for compiling RMarkdown documents)\n\nRTools (for building packages for R)\n\nphantomjs (for saving still images of animated networks, such as transmission chains)\n\n\nTinyTex\nTinyTex is a custom LaTeX distribution, useful when trying to produce PDFs from R.\nSee https://yihui.org/tinytex/ for more informaton.\nTo install TinyTex from R:\n\ninstall.packages('tinytex')\ntinytex::install_tinytex()\n# to uninstall TinyTeX, run tinytex::uninstall_tinytex()\n\n\n\nPandoc\nPandoc is a document converter, a separate software from R. It comes bundled with RStudio and should not need to be downloaded. It helps the process of converting Rmarkdown documents to formats like .pdf and adding complex functionality.\n\n\nRTools\nRTools is a collection of software for building packages for R\nInstall from this website: https://cran.r-project.org/bin/windows/Rtools/\n\n\nphantomjs\nThis is often used to take “screenshots” of webpages. For example when you make a transmission chain with epicontacts package, an HTML file is produced that is interactive and dynamic. If you want a static image, it can be useful to use the webshot package to automate this process. This will require the external program “phantomjs”. You can install phantomjs via the webshot package with the command webshot::install_phantomjs().", + "text": "3.4 Installation\n\nR and RStudio\nHow to install R\nVisit this website https://www.r-project.org/ and download the latest version of R suitable for your computer.\nHow to install RStudio\nVisit this website https://rstudio.com/products/rstudio/download/ and download the latest free Desktop version of RStudio suitable for your computer.\nPermissions\nNote that you should install R and RStudio to a drive where you have read and write permissions. Otherwise, your ability to install R packages (a frequent occurrence) will be impacted. If you encounter problems, try opening RStudio by right-clicking the icon and selecting “Run as administrator”. Other tips can be found in the page R on network drives.\nHow to update R and RStudio\nYour version of R is printed to the R Console at start-up. You can also run sessionInfo().\nTo update R, go to the website mentioned above and re-install R. Alternatively, you can use the installr package (on Windows) by running installr::updateR(). This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the installr documentation.\nBe aware that the old R version will still exist in your computer. You can temporarily run an older version of R by clicking “Tools” -> “Global Options” in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R.\nTo update RStudio, you can go to the website above and re-download RStudio. Another option is to click “Help” -> “Check for Updates” within RStudio, but this may not show the very latest updates.\nTo see which versions of R, RStudio, or packages were used when this Handbook as made, see the page on Editorial and technical notes.\n\n\nOther software you may need to install\n\nTinyTeX (for compiling an RMarkdown document to PDF).\n\nPandoc (for compiling RMarkdown documents).\n\nRTools (for building packages for R).\n\nphantomjs (for saving still images of animated networks, such as transmission chains).\n\n\nTinyTex\nTinyTex is a custom LaTeX distribution, useful when trying to produce PDFs from R.\nSee https://yihui.org/tinytex/ for more informaton.\nTo install TinyTex from R:\n\ninstall.packages('tinytex')\ntinytex::install_tinytex()\n# to uninstall TinyTeX, run tinytex::uninstall_tinytex()\n\n\n\nPandoc\nPandoc is a document converter, a separate software from R. It comes bundled with RStudio and should not need to be downloaded. It helps the process of converting Rmarkdown documents to formats like .pdf and adding complex functionality.\n\n\nRTools\nRTools is a collection of software for building packages for R\nInstall from this website: https://cran.r-project.org/bin/windows/Rtools/\n\n\nphantomjs\nThis is often used to take “screenshots” of webpages. For example when you make a transmission chain with epicontacts package, an HTML file is produced that is interactive and dynamic. If you want a static image, it can be useful to use the webshot package to automate this process. This will require the external program “phantomjs”. You can install phantomjs via the webshot package with the command webshot::install_phantomjs().", "crumbs": [ "Basics", "3  R Basics" @@ -186,7 +186,7 @@ "href": "new_pages/basics.html#rstudio", "title": "3  R Basics", "section": "3.5 RStudio", - "text": "3.5 RStudio\n\nRStudio orientation\nFirst, open RStudio. As their icons can look very similar, be sure you are opening RStudio and not R.\nFor RStudio to work you must also have R installed on the computer (see above for installation instructions).\nRStudio is an interface (GUI) for easier use of R. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) here\nBy default RStudio displays four rectangle panes.\n\n\n\n\n\n\n\n\n\nTIP: If your RStudio displays only one left pane it is because you have no scripts open yet.\nThe Source Pane\nThis pane, by default in the upper-left, is a space to edit, run, and save your scripts. Scripts contain the commands you want to run. This pane can also display datasets (data frames) for viewing.\nFor Stata users, this pane is similar to your Do-file and Data Editor windows.\nThe R Console Pane\nThe R Console, by default the left or lower-left pane in R Studio, is the home of the R “engine”. This is where the commands are actually run and non-graphic outputs and error/warning messages appear. You can directly enter and run commands in the R Console, but realize that these commands are not saved as they are when running commands from a script.\nIf you are familiar with Stata, the R Console is like the Command Window and also the Results Window.\nThe Environment Pane\nThis pane, by default in the upper-right, is most often used to see brief summaries of objects in the R Environment in the current session. These objects could include imported, modified, or created datasets, parameters you have defined (e.g. a specific epi week for the analysis), or vectors or lists you have defined during analysis (e.g. names of regions). You can click on the arrow next to a data frame name to see its variables.\nIn Stata, this is most similar to the Variables Manager window.\nThis pane also contains History where you can see commands that you can previously. It also has a “Tutorial” tab where you can complete interactive R tutorials if you have the learnr package installed. It also has a “Connections” pane for external connections, and can have a “Git” pane if you choose to interface with Github.\nPlots, Viewer, Packages, and Help Pane\nThe lower-right pane includes several important tabs. Typical plot graphics including maps will display in the Plot pane. Interactive or HTML outputs will display in the Viewer pane. The Help pane can display documentation and help files. The Files pane is a browser which can be used to open or delete files. The Packages pane allows you to see, install, update, delete, load/unload R packages, and see which version of the package you have. To learn more about packages see the packages section below.\nThis pane contains the Stata equivalents of the Plots Manager and Project Manager windows.\n\n\nRStudio settings\nChange RStudio settings and appearance in the Tools drop-down menu, by selecting Global Options. There you can change the default settings, including appearance/background color.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nRestart\nIf your R freezes, you can re-start R by going to the Session menu and clicking “Restart R”. This avoids the hassle of closing and opening RStudio. Everything in your R environment will be removed when you do this.\n\n\nKeyboard shortcuts\nSome very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux in the second page of this RStudio user interface cheatsheet.\n\n\n\n\n\n\n\n\nWindows/Linux\nMac\nAction\n\n\n\n\nEsc\nEsc\nInterrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing “+” in the R console)\n\n\nCtrl+s\nCmd+s\nSave (script)\n\n\nTab\nTab\nAuto-complete\n\n\nCtrl + Enter\nCmd + Enter\nRun current line(s)/selection of code\n\n\nCtrl + Shift + C\nCmd + Shift + c\ncomment/uncomment the highlighted lines\n\n\nAlt + -\nOption + -\nInsert <-\n\n\nCtrl + Shift + m\nCmd + Shift + m\nInsert %>%\n\n\nCtrl + l\nCmd + l\nClear the R console\n\n\nCtrl + Alt + b\nCmd + Option + b\nRun from start to current line\n\n\nCtrl + Alt + t\nCmd + Option + t\nRun the current code section (R Markdown)\n\n\nCtrl + Alt + i\nCmd + Shift + r\nInsert code chunk (into R Markdown)\n\n\nCtrl + Alt + c\nCmd + Option + c\nRun current code chunk (R Markdown)\n\n\nup/down arrows in R console\nSame\nToggle through recently run commands\n\n\nShift + up/down arrows in script\nSame\nSelect multiple code lines\n\n\nCtrl + f\nCmd + f\nFind and replace in current script\n\n\nCtrl + Shift + f\nCmd + Shift + f\nFind in files (search/replace across many scripts)\n\n\nAlt + l\nCmd + Option + l\nFold selected code\n\n\nShift + Alt + l\nCmd + Shift + Option+l\nUnfold selected code\n\n\n\nTIP: Use your Tab key when typing to engage RStudio’s auto-complete functionality. This can prevent spelling errors. Press Tab while typing to produce a drop-down menu of likely functions and objects, based on what you have typed so far.", + "text": "3.5 RStudio\n\nRStudio orientation\nFirst, open RStudio.\nAs their icons can look very similar, be sure you are opening RStudio and not R.\nFor RStudio to work you must also have R installed on the computer (see above for installation instructions).\nRStudio is an interface (GUI) for easier use of R. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) here.\nBy default RStudio displays four rectangle panes.\n\n\n\n\n\n\n\n\n\nTIP: If your RStudio displays only one left pane it is because you have no scripts open yet.\nThe Source Pane\nThis pane, by default in the upper-left, is a space to edit, run, and save your scripts. Scripts contain the commands you want to run. This pane can also display datasets (data frames) for viewing.\nFor Stata users, this pane is similar to your Do-file and Data Editor windows.\nThe R Console Pane\nThe R Console, by default the left or lower-left pane in R Studio, is the home of the R “engine”. This is where the commands are actually run and non-graphic outputs and error/warning messages appear. You can directly enter and run commands in the R Console, but realize that these commands are not saved as they are when running commands from a script.\nIf you are familiar with Stata, the R Console is like the Command Window and also the Results Window.\nThe Environment Pane\nThis pane, by default in the upper-right, is most often used to see brief summaries of objects in the R Environment in the current session. These objects could include imported, modified, or created datasets, parameters you have defined (e.g. a specific epi week for the analysis), or vectors or lists you have defined during analysis (e.g. names of regions). You can click on the arrow next to a data frame name to see its variables.\nIn Stata, this is most similar to the Variables Manager window.\nThis pane also contains History where you can see commands that you can previously. It also has a “Tutorial” tab where you can complete interactive R tutorials if you have the learnr package installed. It also has a “Connections” pane for external connections, and can have a “Git” pane if you choose to interface with Github.\nPlots, Viewer, Packages, and Help Pane\nThe lower-right pane includes several important tabs. Typical plot graphics including maps will display in the Plot pane. Interactive or HTML outputs will display in the Viewer pane. The Help pane can display documentation and help files. The Files pane is a browser which can be used to open or delete files. The Packages pane allows you to see, install, update, delete, load/unload R packages, and see which version of the package you have. To learn more about packages see the packages section below.\nThis pane contains the Stata equivalents of the Plots Manager and Project Manager windows.\n\n\nRStudio settings\nChange RStudio settings and appearance in the Tools drop-down menu, by selecting Global Options. There you can change the default settings, including appearance/background color.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nRestart\nIf your R freezes, you can re-start R by going to the Session menu and clicking “Restart R”. This avoids the hassle of closing and opening RStudio.\nCAUTION: Everything in your R environment will be removed when you do this.\n\n\nKeyboard shortcuts\nSome very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux Rstudio user interface cheatsheet.\n\n\n\n\n\n\n\n\n\nWindows/Linux\nMac\nAction\n\n\n\n\n\nEsc\nEsc\nInterrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing “+” in the R console).\n\n\nCtrl+s\nCmd+s\nSave (script).\n\n\nTab\nTab\nAuto-complete.\n\n\nCtrl + Enter\nCmd + Enter\nRun current line(s)/selection of code.\n\n\nCtrl + Shift + C\nCmd + Shift + c\nComment/uncomment the highlighted lines.\n\n\nAlt + -\nOption + -\nInsert <-.\n\n\nCtrl + Shift + m\nCmd + Shift + m\nInsert %>%.\n\n\nCtrl + l\nCmd + l\nClear the R console.\n\n\nCtrl + Alt + b\nCmd + Option + b\nRun from start to current. line\n\n\nCtrl + Alt + t\nCmd + Option + t\nRun the current code section (R Markdown).\n\n\nCtrl + Alt + i\nCmd + Shift + r\nInsert code chunk (into R Markdown).\n\n\nCtrl + Alt + c\nCmd + Option + c\nRun current code chunk (R Markdown).\n\n\nup/down arrows in R console\nSame\nToggle through recently run commands.\n\n\nShift + up/down arrows in script\nSame\nSelect multiple code lines.\n\n\nCtrl + f\nCmd + f\nFind and replace in current script.\n\n\nCtrl + Shift + f\nCmd + Shift + f\nFind in files (search/replace across many scripts).\n\n\nAlt + l\nCmd + Option + l\nFold selected code.\n\n\nShift + Alt + l\nCmd + Shift + Option+l\nUnfold selected code.\n\n\n\nTIP: Use your Tab key when typing to engage RStudio’s auto-complete functionality. This can prevent spelling errors. Press Tab while typing to produce a drop-down menu of likely functions and objects, based on what you have typed so far.", "crumbs": [ "Basics", "3  R Basics" @@ -197,7 +197,7 @@ "href": "new_pages/basics.html#functions", "title": "3  R Basics", "section": "3.6 Functions", - "text": "3.6 Functions\nFunctions are at the core of using R. Functions are how you perform tasks and operations. Many functions come installed with R, many more are available for download in packages (explained in the packages section), and you can even write your own custom functions!\nThis basics section on functions explains:\n\nWhat a function is and how they work\n\nWhat function arguments are\n\nHow to get help understanding a function\n\nA quick note on syntax: In this handbook, functions are written in code-text with open parentheses, like this: filter(). As explained in the packages section, functions are downloaded within packages. In this handbook, package names are written in bold, like dplyr. Sometimes in example code you may see the function name linked explicitly to the name of its package with two colons (::) like this: dplyr::filter(). The purpose of this linkage is explained in the packages section.\n\n\nSimple functions\nA function is like a machine that receives inputs, does some action with those inputs, and produces an output. What the output is depends on the function.\nFunctions typically operate upon some object placed within the function’s parentheses. For example, the function sqrt() calculates the square root of a number:\n\nsqrt(49)\n\n[1] 7\n\n\nThe object provided to a function also can be a column in a dataset (see the Objects section for detail on all the kinds of objects). Because R can store multiple datasets, you will need to specify both the dataset and the column. One way to do this is using the $ notation to link the name of the dataset and the name of the column (dataset$column). In the example below, the function summary() is applied to the numeric column age in the dataset linelist, and the output is a summary of the column’s numeric and missing values.\n\n# Print summary statistics of column 'age' in the dataset 'linelist'\nsummary(linelist$age)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.07 23.00 84.00 86 \n\n\nNOTE: Behind the scenes, a function represents complex additional code that has been wrapped up for the user into one easy command.\n\n\n\nFunctions with multiple arguments\nFunctions often ask for several inputs, called arguments, located within the parentheses of the function, usually separated by commas.\n\nSome arguments are required for the function to work correctly, others are optional\n\nOptional arguments have default settings\n\nArguments can take character, numeric, logical (TRUE/FALSE), and other inputs\n\nHere is a fun fictional function, called oven_bake(), as an example of a typical function. It takes an input object (e.g. a dataset, or in this example “dough”) and performs operations on it as specified by additional arguments (minutes = and temperature =). The output can be printed to the console, or saved as an object using the assignment operator <-.\n\n\n\n\n\n\n\n\n\nIn a more realistic example, the age_pyramid() command below produces an age pyramid plot based on defined age groups and a binary split column, such as gender. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish linelist as the dataframe to use, age_cat5 as the column to count, and gender as the binary column to use for splitting the pyramid by color.\n\n# Create an age pyramid\nage_pyramid(data = linelist, age_group = \"age_cat5\", split_by = \"gender\")\n\n\n\n\n\n\n\n\nThe above command can be equivalently written as below, in a longer style with a new line for each argument. This style can be easier to read, and easier to write “comments” with # to explain each part (commenting extensively is good practice!). To run this longer command you can highlight the entire command and click “Run”, or just place your cursor in the first line and then press the Ctrl and Enter keys simultaneously.\n\n# Create an age pyramid\nage_pyramid(\n data = linelist, # use case linelist\n age_group = \"age_cat5\", # provide age group column\n split_by = \"gender\" # use gender column for two sides of pyramid\n )\n\n\n\n\n\n\n\n\nThe first half of an argument assignment (e.g. data =) does not need to be specified if the arguments are written in a specific order (specified in the function’s documentation). The below code produces the exact same pyramid as above, because the function expects the argument order: data frame, age_group variable, split_by variable.\n\n# This command will produce the exact same graphic as above\nage_pyramid(linelist, \"age_cat5\", \"gender\")\n\nA more complex age_pyramid() command might include the optional arguments to:\n\nShow proportions instead of counts (set proportional = TRUE when the default is FALSE)\n\nSpecify the two colors to use (pal = is short for “palette” and is supplied with a vector of two color names. See the objects page for how the function c() makes a vector)\n\nNOTE: For arguments that you specify with both parts of the argument (e.g. proportional = TRUE), their order among all the arguments does not matter.\n\nage_pyramid(\n linelist, # use case linelist\n \"age_cat5\", # age group column\n \"gender\", # split by gender\n proportional = TRUE, # percents instead of counts\n pal = c(\"orange\", \"purple\") # colors\n )\n\n\n\n\n\n\n\n\n\n\n\nWriting Functions\nR is a language that is oriented around functions, so you should feel empowered to write your own functions. Creating functions brings several advantages:\n\nTo facilitate modular programming - the separation of code in to independent and manageable pieces\n\nReplace repetitive copy-and-paste, which can be error prone\n\nGive pieces of code memorable names\n\nHow to write a function is covered in-depth in the Writing functions page.", + "text": "3.6 Functions\nFunctions are at the core of using R. Functions are how you perform tasks and operations. Many functions come installed with R, many more are available for download in packages (explained in the packages section), and you can even write your own custom functions!\nThis basics section on functions explains:\n\nWhat a function is and how they work.\n\nWhat function arguments are.\n\nHow to get help understanding a function.\n\nA quick note on syntax: In this handbook, functions are written in code-text with open parentheses, like this: filter(). As explained in the packages section, functions are downloaded within packages. In this handbook, package names are written in bold, like dplyr. Sometimes in example code you may see the function name linked explicitly to the name of its package with two colons (::) like this: dplyr::filter(). The purpose of this linkage is explained in the packages section.\n\n\nSimple functions\nA function is like a machine that receives inputs, carries out an action with those inputs, and produces an output. What the output is depends on the function.\nFunctions typically operate upon some object placed within the function’s parentheses. For example, the function sqrt() calculates the square root of a number:\n\nsqrt(49)\n\n[1] 7\n\n\nThe object provided to a function also can be a column in a dataset (see the Objects section for detail on all the kinds of objects). Because R can store multiple datasets, you will need to specify both the dataset and the column. One way to do this is using the $ notation to link the name of the dataset and the name of the column (dataset$column). In the example below, the function summary() is applied to the numeric column age in the dataset linelist, and the output is a summary of the column’s numeric and missing values.\n\n# Print summary statistics of column 'age' in the dataset 'linelist'\nsummary(linelist$age)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.07 23.00 84.00 86 \n\n\nNOTE: Behind the scenes, a function represents complex additional code that has been wrapped up for the user into one easy command.\n\n\n\nFunctions with multiple arguments\nFunctions often ask for several inputs, called arguments, located within the parentheses of the function, usually separated by commas.\n\nSome arguments are required for the function to work correctly, others are optional.\n\nOptional arguments have default settings.\n\nArguments can take character, numeric, logical (TRUE/FALSE), and other inputs.\n\nHere is a fun fictional function, called oven_bake(), as an example of a typical function. It takes an input object (e.g. a dataset, or in this example “dough”) and performs operations on it as specified by additional arguments (minutes = and temperature =). The output can be printed to the console, or saved as an object using the assignment operator <-.\n\n\n\n\n\n\n\n\n\nIn a more realistic example, the age_pyramid() command below produces an age pyramid plot based on defined age groups and a binary split column, such as gender. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish linelist as the data frame to use, age_cat5 as the column to count, and gender as the binary column to use for splitting the pyramid by color.\n\n# Create an age pyramid\nage_pyramid(data = linelist, age_group = \"age_cat5\", split_by = \"gender\")\n\n\n\n\n\n\n\n\nThe above command can be equivalently written as below, in a longer style with a new line for each argument. This style can be easier to read, and easier to write “comments” with # to explain each part (commenting extensively is good practice!). To run this longer command you can highlight the entire command and click “Run”, or just place your cursor in the first line and then press the Ctrl and Enter keys simultaneously.\n\n# Create an age pyramid\nage_pyramid(\n data = linelist, # use case linelist\n age_group = \"age_cat5\", # provide age group column\n split_by = \"gender\" # use gender column for two sides of pyramid\n )\n\n\n\n\n\n\n\n\nThe first half of an argument assignment (e.g. data =) does not need to be specified if the arguments are written in a specific order (specified in the function’s documentation). The below code produces the exact same pyramid as above, because the function expects the argument order: data frame, age_group variable, split_by variable.\n\n# This command will produce the exact same graphic as above\nage_pyramid(linelist, \"age_cat5\", \"gender\")\n\nA more complex age_pyramid() command might include the optional arguments to:\n\nShow proportions instead of counts (set proportional = TRUE when the default is FALSE)\n\nSpecify the two colors to use (pal = is short for “palette” and is supplied with a vector of two color names. See the objects page for how the function c() makes a vector)\n\nNOTE: For arguments that you specify with both parts of the argument (e.g. proportional = TRUE), their order among all the arguments does not matter.\n\nage_pyramid(\n linelist, # use case linelist\n \"age_cat5\", # age group column\n \"gender\", # split by gender\n proportional = TRUE, # percents instead of counts\n pal = c(\"orange\", \"purple\") # colors\n )\n\n\n\n\n\n\n\n\nTIP: Remember that you can put ? before a function to see what arguments the function can take, and which arguments are needed and which arguments have default values. For example `?age_pyramid’.\n\n\n\nWriting Functions\nR is a language that is oriented around functions, so you should feel empowered to write your own functions. Creating functions brings several advantages:\n\nTo facilitate modular programming - the separation of code in to independent and manageable pieces.\n\nReplace repetitive copy-and-paste, which can be error prone.\n\nGive pieces of code memorable names.\n\nHow to write a function is covered in-depth in the Writing functions page.", "crumbs": [ "Basics", "3  R Basics" @@ -208,7 +208,7 @@ "href": "new_pages/basics.html#packages", "title": "3  R Basics", "section": "3.7 Packages", - "text": "3.7 Packages\nPackages contain functions.\nAn R package is a shareable bundle of code and documentation that contains pre-defined functions. Users in the R community develop packages all the time catered to specific problems, it is likely that one can help with your work! You will install and use hundreds of packages in your use of R.\nOn installation, R contains “base” packages and functions that perform common elementary tasks. But many R users create specialized functions, which are verified by the R community and which you can download as a package for your own use. In this handbook, package names are written in bold. One of the more challenging aspects of R is that there are often many functions or packages to choose from to complete a given task.\n\nInstall and load\nFunctions are contained within packages which can be downloaded (“installed”) to your computer from the internet. Once a package is downloaded, it is stored in your “library”. You can then access the functions it contains during your current R session by “loading” the package.\nThink of R as your personal library: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow (“load”) that book from your library.\nIn summary: to use the functions available in an R package, 2 steps must be implemented:\n\nThe package must be installed (once), and\n\nThe package must be loaded (each R session)\n\n\nYour library\nYour “library” is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called “win-library”. For example: R\\win-library\\4.0 (the 4.0 is the R version - you’ll have a different library for each R version you’ve downloaded).\nYou can print the file path to your library by entering .libPaths() (empty parentheses). This becomes especially important if working with R on network drives.\n\n\nInstall from CRAN\nMost often, R users download packages from CRAN. CRAN (Comprehensive R Archive Network) is an online public warehouse of R packages that have been published by R community members.\nAre you worried about viruses and security when downloading a package from CRAN? Read this article on the topic.\n\n\nHow to install and load\nIn this handbook, we suggest using the pacman package (short for “package manager”). It offers a convenient function p_load() which will install a package if necessary and load it for use in the current R session.\nThe syntax quite simple. Just list the names of the packages within the p_load() parentheses, separated by commas. This command will install the rio, tidyverse, and here packages if they are not yet installed, and will load them for use. This makes the p_load() approach convenient and concise if sharing scripts with others. Note that package names are case-sensitive.\n\n# Install (if necessary) and load packages for use\npacman::p_load(rio, tidyverse, here)\n\nNote that we have used the syntax pacman::p_load() which explicitly writes the package name (pacman) prior to the function name (p_load()), connected by two colons ::. This syntax is useful because it also loads the pacman package (assuming it is already installed).\nThere are alternative base R functions that you will see often. The base R function for installing a package is install.packages(). The name of the package to install must be provided in the parentheses in quotes. If you want to install multiple packages in one command, they must be listed within a character vector c().\nNote: this command installs a package, but does not load it for use in the current session.\n\n# install a single package with base R\ninstall.packages(\"tidyverse\")\n\n# install multiple packages with base R\ninstall.packages(c(\"tidyverse\", \"rio\", \"here\"))\n\nInstallation can also be accomplished point-and-click by going to the RStudio “Packages” pane and clicking “Install” and searching for the desired package name.\nThe base R function to load a package for use (after it has been installed) is library(). It can load only one package at a time (another reason to use p_load()). You can provide the package name with or without quotes.\n\n# load packages for use, with base R\nlibrary(tidyverse)\nlibrary(rio)\nlibrary(here)\n\nTo check whether a package in installed and/or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session.\nInstall from Github\nSometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the development version with new features not yet offered in the more stable published CRAN version. These are often hosted on the website github.com in a free, public-facing code “repository”. Read more about Github in the handbook page on [Version control and collaboration with Git and Github].\nTo download R packages from Github, you can use the function p_load_gh() from pacman, which will install the package if necessary, and load it for use in your current R session. Alternatives to install include using the remotes or devtools packages. Read more about all the pacman functions in the package documentation.\nTo install from Github, you have to provide more information. You must provide:\n\nThe Github ID of the repository owner\nThe name of the repository that contains the package\n\n(optional) The name of the “branch” (specific development version) you want to download\n\nIn the examples below, the first word in the quotation marks is the Github ID of the repository owner, after the slash is the name of the repository (the name of the package).\n\n# install/load the epicontacts package from its Github repository\np_load_gh(\"reconhub/epicontacts\")\n\nIf you want to install from a “branch” (version) other than the main branch, add the branch name after an “@”, after the repository name.\n\n# install the \"timeline\" branch of the epicontacts package from Github\np_load_gh(\"reconhub/epicontacts@timeline\")\n\nIf there is no difference between the Github version and the version on your computer, no action will be taken. You can “force” a re-install by instead using p_load_current_gh() with the argument update = TRUE. Read more about pacman in this online vignette\nInstall from ZIP or TAR\nYou could install the package from a URL:\n\npackageurl <- \"https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz\"\ninstall.packages(packageurl, repos=NULL, type=\"source\")\n\nOr, download it to your computer in a zipped file:\nOption 1: using install_local() from the remotes package\n\nremotes::install_local(\"~/Downloads/dplyr-master.zip\")\n\nOption 2: using install.packages() from base R, providing the file path to the ZIP file and setting type = \"source and repos = NULL.\n\ninstall.packages(\"~/Downloads/dplyr-master.zip\", repos=NULL, type=\"source\")\n\n\n\n\nCode syntax\nFor clarity in this handbook, functions are sometimes preceded by the name of their package using the :: symbol in the following way: package_name::function_name()\nOnce a package is loaded for a session, this explicit style is not necessary. One can just use function_name(). However writing the package name is useful when a function name is common and may exist in multiple packages (e.g. plot()). Writing the package name will also load the package if it is not already loaded.\n\n# This command uses the package \"rio\" and its function \"import()\" to import a dataset\nlinelist <- rio::import(\"linelist.xlsx\", which = \"Sheet1\")\n\n\n\nFunction help\nTo read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like ?thefunctionname (put the name of the function after a question mark) and the Help page will appear in the Help pane. Finally, try searching online for resources.\n\n\nUpdate packages\nYou can update packages by re-installing them. You can also click the green “Update” button in your RStudio Packages pane to see which packages have new versions to install. Be aware that your old code may need to be updated if there is a major revision to how a function works!\n\n\nDelete packages\nUse p_delete() from pacman, or remove.packages() from base R. Alternatively, go find the folder which contains your library and manually delete the folder.\n\n\nDependencies\nPackages often depend on other packages to work. These are called dependencies. If a dependency fails to install, then the package depending on it may also fail to install.\nSee the dependencies of a package with p_depends(), and see which packages depend on it with p_depends_reverse()\n\n\nMasked functions\nIt is not uncommon that two or more packages contain the same function name. For example, the package dplyr has a filter() function, but so does the package stats. The default filter() function depends on the order these packages are first loaded in the R session - the later one will be the default for the command filter().\nYou can check the order in your Environment pane of R Studio - click the drop-down for “Global Environment” and see the order of the packages. Functions from packages lower on that drop-down list will mask functions of the same name in packages that appear higher in the drop-down list. When first loading a package, R will warn you in the console if masking is occurring, but this can be easy to miss.\n\n\n\n\n\n\n\n\n\nHere are ways you can fix masking:\n\nSpecify the package name in the command. For example, use dplyr::filter()\n\nRe-arrange the order in which the packages are loaded (e.g. within p_load()), and start a new R session\n\n\n\nDetach / unload\nTo detach (unload) a package, use this command, with the correct package name and only one colon. Note that this may not resolve masking.\n\ndetach(package:PACKAGE_NAME_HERE, unload=TRUE)\n\n\n\nInstall older version\nSee this guide to install an older version of a particular package.\n\n\nSuggested packages\nSee the page on Suggested packages for a listing of packages we recommend for everyday epidemiology.", + "text": "3.7 Packages\nPackages contain functions.\nAn R package is a shareable bundle of code and documentation that contains pre-defined functions. Users in the R community develop packages all the time catered to specific problems, it is likely that one can help with your work! You will install and use hundreds of packages in your use of R.\nOn installation, R contains “base” packages and functions that perform common elementary tasks. But many R users create specialized functions, which are verified by the R community and which you can download as a package for your own use. In this handbook, package names are written in bold. One of the more challenging aspects of R is that there are often many functions or packages to choose from to complete a given task.\n\nInstall and load\nFunctions are contained within packages which can be downloaded (“installed”) to your computer from the internet. Once a package is downloaded, it is stored in your “library”. You can then access the functions it contains during your current R session by “loading” the package.\nThink of R as your personal library: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow, “load”, that book from your library.\nIn summary: to use the functions available in an R package, 2 steps must be implemented:\n\nThe package must be installed (once), and\n\nThe package must be loaded (each R session)\n\n\nYour library\nYour “library” is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called “win-library”. For example: R\\win-library\\4.4.1 (the 4.4.1 is the R version - you’ll have a different library for each R version you’ve downloaded).\nYou can print the file path to your library by entering .libPaths() (empty parentheses). This becomes especially important if working with R on network drives.\n\n\nInstall from CRAN\nMost often, R users download packages from CRAN. CRAN (Comprehensive R Archive Network) is an online public warehouse of R packages that have been published by R community members.\nAre you worried about viruses and security when downloading a package from CRAN? Read this article on the topic.\n\n\nHow to install and load\nIn this handbook, we suggest using the pacman package (short for “package manager”). It offers a convenient function p_load() which will install a package if necessary and load it for use in the current R session.\nThe syntax quite simple. Just list the names of the packages within the p_load() parentheses, separated by commas. This command will install the rio, tidyverse, and here packages if they are not yet installed, and will load them for use. This makes the p_load() approach convenient and concise if sharing scripts with others.\nNote that package names are case-sensitive.\n\n# Install (if necessary) and load packages for use\npacman::p_load(rio, tidyverse, here)\n\nHere we have used the syntax pacman::p_load() which explicitly writes the package name (pacman) prior to the function name (p_load()), connected by two colons ::. This syntax is useful because it also loads the pacman package (assuming it is already installed).\nThere are alternative base R functions that you will see often. The base R function for installing a package is install.packages(). The name of the package to install must be provided in the parentheses in quotes. If you want to install multiple packages in one command, they must be listed within a character vector c().\nNote: this command installs a package, but does not load it for use in the current session.\n\n# install a single package with base R\ninstall.packages(\"tidyverse\")\n\n# install multiple packages with base R\ninstall.packages(c(\"tidyverse\", \"rio\", \"here\"))\n\nInstallation can also be accomplished point-and-click by going to the RStudio “Packages” pane and clicking “Install” and searching for the desired package name.\nThe base R function to load a package for use (after it has been installed) is library(). It can load only one package at a time (another reason to use p_load()). You can provide the package name with or without quotes.\n\n# load packages for use, with base R\nlibrary(tidyverse)\nlibrary(rio)\nlibrary(here)\n\nTo check whether a package is installed or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session.\nInstall from Github\nSometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the development version with new features not yet offered in the more stable published CRAN version. These are often hosted on the website github.com in a free, public-facing code “repository”. Read more about Github in the handbook page on Version control and collaboration with Git and Github.\nTo download R packages from Github, you can use the function p_load_gh() from pacman, which will install the package if necessary, and load it for use in your current R session. Alternatives to install include using the remotes or devtools packages. Read more about all the pacman functions in the package documentation.\nTo install from Github, you have to provide more information. You must provide:\n\nThe Github ID of the repository owner\nThe name of the repository that contains the package\n\nOptional: The name of the “branch” (specific development version) you want to download\n\nIn the examples below, the first word in the quotation marks is the Github ID of the repository owner, after the slash is the name of the repository (the name of the package).\n\n# install/load the epicontacts package from its Github repository\np_load_gh(\"reconhub/epicontacts\")\n\nIf you want to install from a “branch” (version) other than the main branch, add the branch name after an “@”, after the repository name.\n\n# install the \"timeline\" branch of the epicontacts package from Github\np_load_gh(\"reconhub/epicontacts@timeline\")\n\nIf there is no difference between the Github version and the version on your computer, no action will be taken. You can “force” a re-install by instead using p_load_current_gh() with the argument update = TRUE. Read more about pacman in this online vignette\nInstall from ZIP or TAR\nYou could install the package from a URL:\n\npackageurl <- \"https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz\"\ninstall.packages(packageurl, repos=NULL, type=\"source\")\n\nOr, download it to your computer in a zipped file:\nOption 1: using install_local() from the remotes package\n\nremotes::install_local(\"~/Downloads/dplyr-master.zip\")\n\nOption 2: using install.packages() from base R, providing the file path to the ZIP file and setting type = \"source and repos = NULL.\n\ninstall.packages(\"~/Downloads/dplyr-master.zip\", repos=NULL, type=\"source\")\n\n\n\n\nCode syntax\nFor clarity in this handbook, functions are sometimes preceded by the name of their package using the :: symbol in the following way: package_name::function_name()\nOnce a package is loaded for a session, this explicit style is not necessary. One can just use function_name(). However writing the package name is useful when a function name is common and may exist in multiple packages (e.g. plot()). Writing the package name will also load the package if it is not already loaded.\n\n# This command uses the package \"rio\" and its function \"import()\" to import a dataset\nlinelist <- rio::import(\"linelist.xlsx\", which = \"Sheet1\")\n\n\n\nFunction help\nTo read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like ?thefunctionname (for example, to get help for the function p_load you would write ?p_load) and the Help page will appear in the Help pane. Finally, try searching online for resources.\n\n\nUpdate packages\nYou can update packages by re-installing them. You can also click the green “Update” button in your RStudio Packages pane to see which packages have new versions to install. Be aware that your old code may need to be updated if there is a major revision to how a function works!\n\n\nDelete packages\nUse p_delete() from pacman, or remove.packages() from base R.\n\n\nDependencies\nPackages often depend on other packages to work. These are called dependencies. If a dependency fails to install, then the package depending on it may also fail to install.\nSee the dependencies of a package with p_depends(), and see which packages depend on it with p_depends_reverse()\n\n\nMasked functions\nIt is not uncommon that two or more packages contain the same function name. For example, the package dplyr has a filter() function, but so does the package stats. The default filter() function depends on the order these packages are first loaded in the R session - the later one will be the default for the command filter().\nYou can check the order in your Environment pane of R Studio - click the drop-down for “Global Environment” and see the order of the packages. Functions from packages lower on that drop-down list will mask functions of the same name in packages that appear higher in the drop-down list. When first loading a package, R will warn you in the console if masking is occurring, but this can be easy to miss.\n\n\n\n\n\n\n\n\n\nHere are ways you can fix masking:\n\nSpecify the package name in the command. For example, use dplyr::filter()\n\nRe-arrange the order in which the packages are loaded (e.g. within p_load()), and start a new R session\n\n\n\nDetach / unload\nTo detach (unload) a package, use this command, with the correct package name and only one colon. Note that this may not resolve masking.\n\ndetach(package:PACKAGE_NAME_HERE, unload=TRUE)\n\n\n\nInstall older version\nSee this guide to install an older version of a particular package.\n\n\nSuggested packages\nSee the page on Suggested packages for a listing of packages we recommend for everyday epidemiology.", "crumbs": [ "Basics", "3  R Basics" @@ -219,7 +219,7 @@ "href": "new_pages/basics.html#scripts", "title": "3  R Basics", "section": "3.8 Scripts", - "text": "3.8 Scripts\nScripts are a fundamental part of programming. They are documents that hold your commands (e.g. functions to create and modify datasets, print visualizations, etc). You can save a script and run it again later. There are many advantages to storing and running your commands from a script (vs. typing commands one-by-one into the R console “command line”):\n\nPortability - you can share your work with others by sending them your scripts\n\nReproducibility - so that you and others know exactly what you did\n\nVersion control - so you can track changes made by yourself or colleagues\n\nCommenting/annotation - to explain to your colleagues what you have done\n\n\nCommenting\nIn a script you can also annotate (“comment”) around your R code. Commenting is helpful to explain to yourself and other readers what you are doing. You can add a comment by typing the hash symbol (#) and writing your comment after it. The commented text will appear in a different color than the R code.\nAny code written after the # will not be run. Therefore, placing a # before code is also a useful way to temporarily block a line of code (“comment out”) if you do not want to delete it). You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac).\n\n# A comment can be on a line by itself\n# import data\nlinelist <- import(\"linelist_raw.xlsx\") %>% # a comment can also come after code\n# filter(age > 50) # It can also be used to deactivate / remove a line of code\n count()\n\n\nComment on what you are doing and on why you are doing it.\n\nBreak your code into logical sections\n\nAccompany your code with a text step-by-step description of what you are doing (e.g. numbered steps)\n\n\n\nStyle\nIt is important to be conscious of your coding style - especially if working on a team. We advocate for the tidyverse style guide. There are also packages such as styler and lintr which help you conform to this style.\nA few very basic points to make your code readable to others:\n* When naming objects, use only lowercase letters, numbers, and underscores _, e.g. my_data\n* Use frequent spaces, including around operators, e.g. n = 1 and age_new <- age_old + 3\n\n\nExample Script\nBelow is an example of a short R script. Remember, the better you succinctly explain your code in comments, the more your colleagues will like you!\n\n\n\n\n\n\n\n\n\n\n\n\nR markdown\nAn R markdown script is a type of R script in which the script itself becomes an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports. Even this website and handbook is produced with R markdown scripts!\nIt is worth noting that beginner R users can also use R Markdown - do not be intimidated! To learn more, see the handbook page on Reports with R Markdown documents.\n\n\n\nR notebooks\nThere is no difference between writing in a Rmarkdown vs an R notebook. However the execution of the document differs slightly. See this site for more details.\n\n\n\nShiny\nShiny apps/websites are contained within one script, which must be named app.R. This file has three components:\n\nA user interface (ui)\n\nA server function\n\nA call to the shinyApp function\n\nSee the handbook page on Dashboards with Shiny, or this online tutorial: Shiny tutorial\nIn older times, the above file was split into two files (ui.R and server.R)\n\n\nCode folding\nYou can collapse portions of code to make your script easier to read.\nTo do this, create a text header with #, write your header, and follow it with at least 4 of either dashes (-), hashes (#) or equals (=). When you have done this, a small arrow will appear in the “gutter” to the left (by the row number). You can click this arrow and the code below until the next header will collapse and a dual-arrow icon will appear in its place.\nTo expand the code, either click the arrow in the gutter again, or the dual-arrow icon. There are also keyboard shortcuts as explained in the RStudio section of this page.\nBy creating headers with #, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more # symbols, for example # for primary, ## for seconary, and ### for tertiary headers.\nBelow are two versions of an example script. On the left is the original with commented headers. On the right, four dashes have been written after each header, making them collapsible. Two of them have been collapsed, and you can see that the Table of Contents at the bottom now shows each section.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOther areas of code that are automatically eligible for folding include “braced” regions with brackets { } such as function definitions or conditional blocks (if else statements). You can read more about code folding at the RStudio site.", + "text": "3.8 Scripts\nScripts are a fundamental part of programming. They are documents that hold your commands (e.g. functions to create and modify datasets, print visualizations, etc). You can save a script and run it again later. There are many advantages to storing and running your commands from a script (vs. typing commands one-by-one into the R console “command line”):\n\nPortability - you can share your work with others by sending them your scripts.\n\nReproducibility - so that you and others know exactly what you did.\n\nVersion control - so you can track changes made by yourself or colleagues.\n\nCommenting/annotation - to explain to your colleagues what you have done.\n\n\nCommenting\nIn a script you can also annotate (“comment”) around your R code. Commenting is helpful to explain to yourself and other readers what you are doing. You can add a comment by typing the hash symbol (#) and writing your comment after it. The commented text will appear in a different color than the R code.\nAny code written after the # will not be run. Therefore, placing a # before code is also a useful way to temporarily block a line of code (“comment out”) if you do not want to delete it. You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac).\n\n# A comment can be on a line by itself\n# import data\nlinelist <- import(\"linelist_raw.xlsx\") %>% # a comment can also come after code\n# filter(age > 50) # It can also be used to deactivate / remove a line of code\n count()\n\nThere are a few general ideas to follow when writing your scripts in order to make them accessible. - Add comments on what you are doing and on why you are doing it.\n- Break your code into logical sections.\n- Accompany your code with a text step-by-step description of what you are doing (e.g. numbered steps).\n\n\nStyle\nIt is important to be conscious of your coding style - especially if working on a team. We advocate for the tidyverse style guide. There are also packages such as styler and lintr which help you conform to this style.\nA few very basic points to make your code readable to others:\n* When naming objects, use only lowercase letters, numbers, and underscores _, e.g. my_data\n* Use frequent spaces, including around operators, e.g. n = 1 and age_new <- age_old + 3\n\n\nExample Script\nBelow is an example of a short R script. Remember, the better you succinctly explain your code in comments, the more your colleagues will like you!\n\n\n\n\n\n\n\n\n\n\n\n\nR markdown and Quarto\nAn R Markdown or Quarto script are types of R script in which the script itself becomes an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports.\nEven this website and handbook is produced with Quarto scripts!\nIt is worth noting that beginner R users can also use R Markdown - do not be intimidated! To learn more, see the handbook page on Reports with R Markdown documents.\n\n\n\nR notebooks\nThere is no difference between writing in a Rmarkdown vs an R notebook. However the execution of the document differs slightly. See this site for more details.\n\n\n\nShiny\nShiny apps/websites are contained within one script, which must be named app.R. This file has three components:\n\nA user interface (ui).\n\nA server function.\n\nA call to the shinyApp function.\n\nSee the handbook page on Dashboards with Shiny, or this online tutorial: Shiny tutorial\nIn previous versions, the above file was split into two files (ui.R and server.R)\n\n\nCode folding\nYou can collapse portions of code to make your script easier to read.\nTo do this, create a text header with #, write your header, and follow it with at least 4 of either dashes (-), hashes (#) or equals (=). When you have done this, a small arrow will appear in the “gutter” to the left (by the row number). You can click this arrow and the code below until the next header will collapse and a dual-arrow icon will appear in its place.\nTo expand the code, either click the arrow in the gutter again, or the dual-arrow icon. There are also keyboard shortcuts as explained in the RStudio section of this page.\nBy creating headers with #, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more # symbols, for example # for primary, ## for secondary, and ### for tertiary headers.\nBelow are two versions of an example script. On the left is the original with commented headers. On the right, four dashes have been written after each header, making them collapsible. Two of them have been collapsed, and you can see that the Table of Contents at the bottom now shows each section.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOther areas of code that are automatically eligible for folding include “braced” regions with brackets { } such as function definitions or conditional blocks (if else statements). You can read more about code folding at the RStudio site.", "crumbs": [ "Basics", "3  R Basics" @@ -230,7 +230,7 @@ "href": "new_pages/basics.html#working-directory", "title": "3  R Basics", "section": "3.9 Working directory", - "text": "3.9 Working directory\nThe working directory is the root folder location used by R for your work - where R looks for and saves files by default. By default, it will save new files and outputs to this location, and will look for files to import (e.g. datasets) here as well.\nThe working directory appears in grey text at the top of the RStudio Console pane. You can also print the current working directory by running getwd() (leave the parentheses empty).\n\n\n\n\n\n\n\n\n\n\nRecommended approach\nSee the page on R projects for details on our recommended approach to managing your working directory.\nA common, efficient, and trouble-free way to manage your working directory and file paths is to combine these 3 elements in an R project-oriented workflow:\n\nAn R Project to store all your files (see page on R projects)\n\nThe here package to locate files (see page on Import and export)\n\nThe rio package to import/export files (see page on Import and export)\n\n\n\n\nSet by command\nUntil recently, many people learning R were taught to begin their scripts with a setwd() command. Please instead consider using an [R project][r_projects.qmd]-oriented workflow and read the reasons for not using setwd(). In brief, your work becomes specific to your computer, file paths used to import and export files become “brittle”, and this severely hinders collaboration and use of your code on any other computer. There are easy alternatives!\nAs noted above, although we do not recommend this approach in most circumstances, you can use the command setwd() with the desired folder file path in quotations, for example:\n\nsetwd(\"C:/Documents/R Files/My analysis\")\n\nDANGER: Setting a working directory with setwd() can be “brittle” if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory (with the here package).\n\n\n\nSet manually\nTo set the working directory manually (the point-and-click equivalent of setwd()), click the Session drop-down menu and go to “Set Working Directory” and then “Choose Directory”. This will set the working directory for that specific R session. Note: if using this approach, you will have to do this manually each time you open RStudio.\n\n\n\nWithin an R project\nIf using an R project, the working directory will default to the R project root folder that contains the “.rproj” file. This will apply if you open RStudio by clicking open the R Project (the file with “.rproj” extension).\n\n\n\nWorking directory in an R markdown\nIn an R markdown script, the default working directory is the folder the Rmarkdown file (.Rmd) is saved within. If using an R project and here package, this does not apply and the working directory will be here() as explained in the R projects page.\nIf you want to change the working directory of a stand-alone R markdown (not in an R project), if you use setwd() this will only apply to that specific code chunk. To make the change for all code chunks in an R markdown, edit the setup chunk to add the root.dir = parameter, such as below:\n\nknitr::opts_knit$set(root.dir = 'desired/directorypath')\n\nIt is much easier to just use the R markdown within an R project and use the here package.\n\n\n\nProviding file paths\nPerhaps the most common source of frustration for an R beginner (at least on a Windows machine) is typing in a file path to import or export data. There is a thorough explanation of how to best input file paths in the Import and export page, but here are a few key points:\nBroken paths\nBelow is an example of an “absolute” or “full address” file path. These will likely break if used by another computer. One exception is if you are using a shared/network drive.\nC:/Users/Name/Document/Analytic Software/R/Projects/Analysis2019/data/March2019.csv \nSlash direction\nIf typing in a file path, be aware the direction of the slashes. Use forward slashes (/) to separate the components (“data/provincial.csv”). For Windows users, the default way that file paths are displayed is with back slashes (\\) - so you will need to change the direction of each slash. If you use the here package as described in the R projects page the slash direction is not an issue.\nRelative file paths\nWe generally recommend providing “relative” filepaths instead - that is, the path relative to the root of your R Project. You can do this using the here package as explained in the R projects page. A relativel filepath might look like this:\n\n# Import csv linelist from the data/linelist/clean/ sub-folders of an R project\nlinelist <- import(here(\"data\", \"clean\", \"linelists\", \"marin_country.csv\"))\n\nEven if using relative file paths within an R project, you can still use absolute paths to import/export data outside your R project.", + "text": "3.9 Working directory\nThe working directory is the root folder location used by R for your work - where R looks for and saves files by default. By default, it will save new files and outputs to this location, and will look for files to import (e.g. datasets) here as well.\nThe working directory appears in grey text at the top of the RStudio Console pane. You can also print the current working directory by running getwd() (leave the parentheses empty).\n\n\n\n\n\n\n\n\n\n\nRecommended approach\nSee the page on R projects for details on our recommended approach to managing your working directory.\n\nA common, efficient, and trouble-free way to manage your working directory and file paths is to combine these 3 elements in an R project-oriented workflow:\n\nAn R Project to store all your files (see page on R projects)\n\nThe here package to locate files (see page on Import and export)\n\nThe rio package to import/export files (see page on Import and export)\n\n\n\n\nSet by command\nUntil recently, many people learning R were taught to begin their scripts with a setwd() command.\nPlease instead consider using an R project-oriented workflow and read the reasons for not using setwd().\nIn brief, your work becomes specific to your computer. This means that file paths used to import and export files need to be changed if used on a different computer, or by different collaborators.\nAs noted above, although we do not recommend this approach in most circumstances, you can use the command setwd() with the desired folder file path in quotations, for example:\n\nsetwd(\"C:/Documents/R Files/My analysis\")\n\nDANGER: Setting a working directory with setwd() can be “brittle” if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory, such as with the [here package].\n\n\n\nSet manually\nTo set the working directory manually (the point-and-click equivalent of setwd()), click the Session drop-down menu and go to “Set Working Directory” and then “Choose Directory”. This will set the working directory for that specific R session. Note: if using this approach, you will have to do this manually each time you open RStudio.\n\n\n\nWithin an R project\nIf using an R project, the working directory will default to the R project root folder that contains the “.rproj” file. This will apply if you open RStudio by clicking open the R Project (the file with “.rproj” extension).\n\n\n\nWorking directory in an R markdown\nIn an R markdown script, the default working directory is the folder the Rmarkdown file (.Rmd) is saved within. If using an R project and here package, this does not apply and the working directory will be here() as explained in the R projects page.\nIf you want to change the working directory of a stand-alone R markdown (not in an R project), if you use setwd() this will only apply to that specific code chunk. To make the change for all code chunks in an R markdown, edit the setup chunk to add the root.dir = parameter, such as below:\n\nknitr::opts_knit$set(root.dir = 'desired/directorypath')\n\nIt is much easier to just use the R markdown within an R project and use the here package.\n\n\n\nProviding file paths\nPerhaps the most common source of frustration for an R beginner (at least on a Windows machine) is typing in a file path to import or export data. There is a thorough explanation of how to best input file paths in the Import and export page, but here are a few key points:\nBroken paths\nBelow is an example of an “absolute” or “full address” file path. These will likely break if used by another computer. One exception is if you are using a shared/network drive.\nC:/Users/Name/Document/Analytic Software/R/Projects/Analysis2019/data/March2019.csv \nSlash direction\nIf typing in a file path, be aware the direction of the slashes.\nUse forward slashes (/) to separate the components (“data/provincial.csv”). For Windows users, the default way that file paths are displayed is with back slashes (\\) - so you will need to change the direction of each slash. If you use the here package as described in the R projects page the slash direction is not an issue.\nRelative file paths\nWe generally recommend providing “relative” filepaths instead - that is, the path relative to the root of your R Project. You can do this using the here package as explained in the R projects page. A relativel filepath might look like this:\n\n# Import csv linelist from the data/linelist/clean/ sub-folders of an R project\nlinelist <- import(here(\"data\", \"clean\", \"linelists\", \"marin_country.csv\"))\n\nEven if using relative file paths within an R project, you can still use absolute paths to import and export data outside your R project.", "crumbs": [ "Basics", "3  R Basics" @@ -241,7 +241,7 @@ "href": "new_pages/basics.html#objects", "title": "3  R Basics", "section": "3.10 Objects", - "text": "3.10 Objects\nEverything in R is an object, and R is an “object-oriented” language. These sections will explain:\n\nHow to create objects (<-)\nTypes of objects (e.g. data frames, vectors..)\n\nHow to access subparts of objects (e.g. variables in a dataset)\n\nClasses of objects (e.g. numeric, logical, integer, double, character, factor)\n\n\n\nEverything is an object\nThis section is adapted from the R4Epis project.\nEverything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are objects which are assigned a name and can be referenced in later commands.\nAn object exists when you have assigned it a value (see the assignment section below). When it is assigned a value, the object appears in the Environment (see the upper right pane of RStudio). It can then be operated upon, manipulated, changed, and re-defined.\n\n\n\nDefining objects (<-)\nCreate objects by assigning them a value with the <- operator.\nYou can think of the assignment operator <- as the words “is defined as”. Assignment commands generally follow a standard order:\nobject_name <- value (or process/calculation that produce a value)\nFor example, you may want to record the current epidemiological reporting week as an object for reference in later code. In this example, the object current_week is created when it is assigned the value \"2018-W10\" (the quote marks make this a character value). The object current_week will then appear in the RStudio Environment pane (upper-right) and can be referenced in later commands.\nSee the R commands and their output in the boxes below.\n\ncurrent_week <- \"2018-W10\" # this command creates the object current_week by assigning it a value\ncurrent_week # this command prints the current value of current_week object in the console\n\n[1] \"2018-W10\"\n\n\nNOTE: Note the [1] in the R console output is simply indicating that you are viewing the first item of the output\nCAUTION: An object’s value can be over-written at any time by running an assignment command to re-define its value. Thus, the order of the commands run is very important.\nThe following command will re-define the value of current_week:\n\ncurrent_week <- \"2018-W51\" # assigns a NEW value to the object current_week\ncurrent_week # prints the current value of current_week in the console\n\n[1] \"2018-W51\"\n\n\nEquals signs =\nYou will also see equals signs in R code:\n\nA double equals sign == between two objects or values asks a logical question: “is this equal to that?”.\n\nYou will also see equals signs within functions used to specify values of function arguments (read about these in sections below), for example max(age, na.rm = TRUE).\n\nYou can use a single equals sign = in place of <- to create and define objects, but this is discouraged. You can read about why this is discouraged here.\n\nDatasets\nDatasets are also objects (typically “dataframes”) and must be assigned names when they are imported. In the code below, the object linelist is created and assigned the value of a CSV file imported with the rio package and its import() function.\n\n# linelist is created and assigned the value of the imported CSV file\nlinelist <- import(\"my_linelist.csv\")\n\nYou can read more about importing and exporting datasets with the section on Import and export.\nCAUTION: A quick note on naming of objects:\n\nObject names must not contain spaces, but you should use underscore (_) or a period (.) instead of a space.\n\nObject names are case-sensitive (meaning that Dataset_A is different from dataset_A).\nObject names must begin with a letter (cannot begin with a number like 1, 2 or 3).\n\nOutputs\nOutputs like tables and plots provide an example of how outputs can be saved as objects, or just be printed without being saved. A cross-tabulation of gender and outcome using the base R function table() can be printed directly to the R console (without being saved).\n\n# printed to R console only\ntable(linelist$gender, linelist$outcome)\n\n \n Death Recover\n f 1227 953\n m 1228 950\n\n\nBut the same table can be saved as a named object. Then, optionally, it can be printed.\n\n# save\ngen_out_table <- table(linelist$gender, linelist$outcome)\n\n# print\ngen_out_table\n\n \n Death Recover\n f 1227 953\n m 1228 950\n\n\nColumns\nColumns in a dataset are also objects and can be defined, over-written, and created as described below in the section on Columns.\nYou can use the assignment operator from base R to create a new column. Below, the new column bmi (Body Mass Index) is created, and for each row the new value is result of a mathematical operation on the row’s value in the wt_kg and ht_cm columns.\n\n# create new \"bmi\" column using base R syntax\nlinelist$bmi <- linelist$wt_kg / (linelist$ht_cm/100)^2\n\nHowever, in this handbook, we emphasize a different approach to defining columns, which uses the function mutate() from the dplyr package and piping with the pipe operator (%>%). The syntax is easier to read and there are other advantages explained in the page on Cleaning data and core functions. You can read more about piping in the Piping section below.\n\n# create new \"bmi\" column using dplyr syntax\nlinelist <- linelist %>% \n mutate(bmi = wt_kg / (ht_cm/100)^2)\n\n\n\n\nObject structure\nObjects can be a single piece of data (e.g. my_number <- 24), or they can consist of structured data.\nThe graphic below is borrowed from this online R tutorial. It shows some common data structures and their names. Not included in this image is spatial data, which is discussed in the GIS basics page.\n\n\n\n\n\n\n\n\n\nIn epidemiology (and particularly field epidemiology), you will most commonly encounter data frames and vectors:\n\n\n\n\n\n\n\n\nCommon structure\nExplanation\nExample\n\n\n\n\nVectors\nA container for a sequence of singular objects, all of the same class (e.g. numeric, character).\n“Variables” (columns) in data frames are vectors (e.g. the column age_years).\n\n\nData Frames\nVectors (e.g. columns) that are bound together that all have the same number of rows.\nlinelist is a data frame.\n\n\n\nNote that to create a vector that “stands alone” (is not part of a data frame) the function c() is used to combine the different elements. For example, if creating a vector of colors plot’s color scale: vector_of_colors <- c(\"blue\", \"red2\", \"orange\", \"grey\")\n\n\n\nObject classes\nAll the objects stored in R have a class which tells R how to handle the object. There are many possible classes, but common ones include:\n\n\n\n4 Class\nCharacter\n5 Explanation\nThese are text/words/sentences “within quotation marks”. Math cannot be done on these objects.\n6 Examples\n“Character objects are in quotation marks”\n\n\n\n\n\n\n\n\n\nInteger\nNumbers that are whole only (no decimals)\n-5, 14, or 2000\n\n\n\nNumeric\nThese are numbers and can include decimals. If within quotation marks they will be considered character class.\n23.1 or 14\n\n\n\nFactor\nThese are vectors that have a specified order or hierarchy of values\nAn variable of economic status with ordered values\n\n\n\nDate\nOnce R is told that certain data are Dates, these data can be manipulated and displayed in special ways. See the page on Working with dates for more information. | 2018-04-12 or 15/3/1954 or Wed 4 Jan 1980\n\n\n\n\nLogical\nValues must be one of the two special values TRUE or FALSE (note these are not “TRUE” and “FALSE” in quotation marks)\nTRUE or FALSE\n\n\n\ndata.frame\nA data frame is how R stores a typical dataset. It consists of vectors (columns) of data bound together, that all have the same number of observations (rows).\nThe example AJS dataset named linelist_raw contains 68 variables with 300 observations (rows) each.\n\n\n\ntibble\ntibbles are a variation on data frame, the main operational difference being that they print more nicely to the console (display first 10 rows and only columns that fit on the screen)\nAny data frame, list, or matrix can be converted to a tibble with as_tibble()\n\n\n\nlist\nA list is like vector, but holds other objects that can be other different classes\nA list could hold a single number, and a dataframe, and a vector, and even another list within it!\n\n\n\n\nYou can test the class of an object by providing its name to the function class(). Note: you can reference a specific column within a dataset using the $ notation to separate the name of the dataset and the name of the column.\n\nclass(linelist) # class should be a data frame or tibble\n\n[1] \"data.frame\"\n\nclass(linelist$age) # class should be numeric\n\n[1] \"numeric\"\n\nclass(linelist$gender) # class should be character\n\n[1] \"character\"\n\n\nSometimes, a column will be converted to a different class automatically by R. Watch out for this! For example, if you have a vector or column of numbers, but a character value is inserted… the entire column will change to class character.\n\nnum_vector <- c(1,2,3,4,5) # define vector as all numbers\nclass(num_vector) # vector is numeric class\n\n[1] \"numeric\"\n\nnum_vector[3] <- \"three\" # convert the third element to a character\nclass(num_vector) # vector is now character class\n\n[1] \"character\"\n\n\nOne common example of this is when manipulating a data frame in order to print a table - if you make a total row and try to paste/glue together percents in the same cell as numbers (e.g. 23 (40%)), the entire numeric column above will convert to character and can no longer be used for mathematical calculations.Sometimes, you will need to convert objects or columns to another class.\n\n\n\n7 Function\nas.character()\n8 Action\nConverts to character class\n\n\n\n\n\n\n\n\nas.numeric()\nConverts to numeric class\n\n\n\nas.integer()\nConverts to integer class\n\n\n\nas.Date()\nConverts to Date class - Note: see section on dates for details\n\n\n\nfactor()\nConverts to factor - Note: re-defining order of value levels requires extra arguments\n\n\n\n\nLikewise, there are base R functions to check whether an object IS of a specific class, such as is.numeric(), is.character(), is.double(), is.factor(), is.integer()\nHere is more online material on classes and data structures in R.\n\n\n\nColumns/Variables ($)\nA column in a data frame is technically a “vector” (see table above) - a series of values that must all be the same class (either character, numeric, logical, etc).\nA vector can exist independent of a data frame, for example a vector of column names that you want to include as explanatory variables in a model. To create a “stand alone” vector, use the c() function as below:\n\n# define the stand-alone vector of character values\nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n# print the values in this named vector\nexplanatory_vars\n\n[1] \"gender\" \"fever\" \"chills\" \"cough\" \"aches\" \"vomit\" \n\n\nColumns in a data frame are also vectors and can be called, referenced, extracted, or created using the $ symbol. The $ symbol connects the name of the column to the name of its data frame. In this handbook, we try to use the word “column” instead of “variable”.\n\n# Retrieve the length of the vector age_years\nlength(linelist$age) # (age is a column in the linelist data frame)\n\nBy typing the name of the dataframe followed by $ you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes!\n\n\n\n\n\n\n\n\n\nADVANCED TIP: Some more complex objects (e.g. a list, or an epicontacts object) may have multiple levels which can be accessed through multiple dollar signs. For example epicontacts$linelist$date_onset\n\n\n\nAccess/index with brackets ([ ])\nYou may need to view parts of objects, also called “indexing”, which is often done using the square brackets [ ]. Using $ on a dataframe to access a column is also a type of indexing.\n\nmy_vector <- c(\"a\", \"b\", \"c\", \"d\", \"e\", \"f\") # define the vector\nmy_vector[5] # print the 5th element\n\n[1] \"e\"\n\n\nSquare brackets also work to return specific parts of an returned output, such as the output of a summary() function:\n\n# All of the summary\nsummary(linelist$age)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.07 23.00 84.00 86 \n\n# Just the second element of the summary, with name (using only single brackets)\nsummary(linelist$age)[2]\n\n1st Qu. \n 6 \n\n# Just the second element, without name (using double brackets)\nsummary(linelist$age)[[2]]\n\n[1] 6\n\n# Extract an element by name, without showing the name\nsummary(linelist$age)[[\"Median\"]]\n\n[1] 13\n\n\nBrackets also work on data frames to view specific rows and columns. You can do this using the syntax dataframe[rows, columns]:\n\n# View a specific row (2) from dataset, with all columns (don't forget the comma!)\nlinelist[2,]\n\n# View all rows, but just one column\nlinelist[, \"date_onset\"]\n\n# View values from row 2 and columns 5 through 10\nlinelist[2, 5:10] \n\n# View values from row 2 and columns 5 through 10 and 18\nlinelist[2, c(5:10, 18)] \n\n# View rows 2 through 20, and specific columns\nlinelist[2:20, c(\"date_onset\", \"outcome\", \"age\")]\n\n# View rows and columns based on criteria\n# *** Note the dataframe must still be named in the criteria!\nlinelist[linelist$age > 25 , c(\"date_onset\", \"outcome\", \"age\")]\n\n# Use View() to see the outputs in the RStudio Viewer pane (easier to read) \n# *** Note the capital \"V\" in View() function\nView(linelist[2:20, \"date_onset\"])\n\n# Save as a new object\nnew_table <- linelist[2:20, c(\"date_onset\")] \n\nNote that you can also achieve the above row/column indexing on data frames and tibbles using dplyr syntax (functions filter() for rows, and select() for columns). Read more about these core functions in the Cleaning data and core functions page.\nTo filter based on “row number”, you can use the dplyr function row_number() with open parentheses as part of a logical filtering statement. Often you will use the %in% operator and a range of numbers as part of that logical statement, as shown below. To see the first N rows, you can also use the special dplyr function head().\n\n# View first 100 rows\nlinelist %>% head(100)\n\n# Show row 5 only\nlinelist %>% filter(row_number() == 5)\n\n# View rows 2 through 20, and three specific columns (note no quotes necessary on column names)\nlinelist %>% filter(row_number() %in% 2:20) %>% select(date_onset, outcome, age)\n\nWhen indexing an object of class list, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list.\nBrackets can also be written after one another, as demonstrated below.\nThis visual explanation of lists indexing, with pepper shakers is humorous and helpful.\n\n# define demo list\nmy_list <- list(\n # First element in the list is a character vector\n hospitals = c(\"Central\", \"Empire\", \"Santa Anna\"),\n \n # second element in the list is a data frame of addresses\n addresses = data.frame(\n street = c(\"145 Medical Way\", \"1048 Brown Ave\", \"999 El Camino\"),\n city = c(\"Andover\", \"Hamilton\", \"El Paso\")\n )\n )\n\nHere is how the list looks when printed to the console. See how there are two named elements:\n\nhospitals, a character vector\n\naddresses, a data frame of addresses\n\n\nmy_list\n\n$hospitals\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\n$addresses\n street city\n1 145 Medical Way Andover\n2 1048 Brown Ave Hamilton\n3 999 El Camino El Paso\n\n\nNow we extract, using various methods:\n\nmy_list[1] # this returns the element in class \"list\" - the element name is still displayed\n\n$hospitals\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\nmy_list[[1]] # this returns only the (unnamed) character vector\n\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\nmy_list[[\"hospitals\"]] # you can also index by name of the list element\n\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\nmy_list[[1]][3] # this returns the third element of the \"hospitals\" character vector\n\n[1] \"Santa Anna\"\n\nmy_list[[2]][1] # This returns the first column (\"street\") of the address data frame\n\n street\n1 145 Medical Way\n2 1048 Brown Ave\n3 999 El Camino\n\n\n\n\n\nRemove objects\nYou can remove individual objects from your R environment by putting the name in the rm() function (no quote marks):\n\nrm(object_name)\n\nYou can remove all objects (clear your workspace) by running:\n\nrm(list = ls(all = TRUE))", + "text": "3.10 Objects\nEverything in R is an object, and R is an “object-oriented” language. These sections will explain:\n\nHow to create objects (<-).\nTypes of objects (e.g. data frames, vectors..).\n\nHow to access subparts of objects (e.g. variables in a dataset).\n\nClasses of objects (e.g. numeric, logical, integer, double, character, factor).\n\n\n\nEverything is an object\nThis section is adapted from the R4Epis project.\n\nEverything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are objects which are assigned a name and can be referenced in later commands.\nAn object exists when you have assigned it a value (see the assignment section below). When it is assigned a value, the object appears in the Environment (see the upper right pane of RStudio). It can then be operated upon, manipulated, changed, and re-defined.\n\n\n\nDefining objects (<-)\nCreate objects by assigning them a value with the <- operator.\nYou can think of the assignment operator <- as the words “is defined as”. Assignment commands generally follow a standard order:\nobject_name <- value (or process/calculation that produce a value)\nFor example, you may want to record the current epidemiological reporting week as an object for reference in later code. In this example, the object current_week is created when it is assigned the value \"2018-W10\" (the quote marks make this a character value). The object current_week will then appear in the RStudio Environment pane (upper-right) and can be referenced in later commands.\nSee the R commands and their output in the boxes below.\n\ncurrent_week <- \"2018-W10\" # this command creates the object current_week by assigning it a value\ncurrent_week # this command prints the current value of current_week object in the console\n\n[1] \"2018-W10\"\n\n\nNOTE: Note the [1] in the R console output is simply indicating that you are viewing the first item of the output\nCAUTION: An object’s value can be over-written at any time by running an assignment command to re-define its value. Thus, the order of the commands run is very important.\nThe following command will re-define the value of current_week:\n\ncurrent_week <- \"2018-W51\" # assigns a NEW value to the object current_week\ncurrent_week # prints the current value of current_week in the console\n\n[1] \"2018-W51\"\n\n\nEquals signs =\nYou will also see equals signs in R code:\n\nA double equals sign == between two objects or values asks a logical question: “is this equal to that?”.\n\nYou will also see equals signs within functions used to specify values of function arguments (read about these in sections below), for example max(age, na.rm = TRUE).\n\nYou can use a single equals sign = in place of <- to create and define objects, but this is discouraged. You can read about why this is discouraged here.\n\nDatasets\nDatasets are also objects (typically “data frames”) and must be assigned names when they are imported. In the code below, the object linelist is created and assigned the value of a CSV file imported with the rio package and its import() function.\n\n# linelist is created and assigned the value of the imported CSV file\nlinelist <- import(\"my_linelist.csv\")\n\nYou can read more about importing and exporting datasets with the section on Import and export.\nCAUTION: A quick note on naming of objects:\n\nObject names must not contain spaces, but you should use underscore (_) or a period (.) instead of a space.\n\nObject names are case-sensitive (meaning that Dataset_A is different from dataset_A).\nObject names must begin with a letter (they cannot begin with a number like 1, 2 or 3).\n\nOutputs\nOutputs like tables and plots provide an example of how outputs can be saved as objects, or just be printed without being saved. A cross-tabulation of gender and outcome using the base R function table() can be printed directly to the R console (without being saved).\n\n# printed to R console only\ntable(linelist$gender, linelist$outcome)\n\n \n Death Recover\n f 1227 953\n m 1228 950\n\n\nBut the same table can be saved as a named object. Then, optionally, it can be printed.\n\n# save\ngen_out_table <- table(linelist$gender, linelist$outcome)\n\n# print\ngen_out_table\n\n \n Death Recover\n f 1227 953\n m 1228 950\n\n\nColumns\nColumns in a dataset are also objects and can be defined, over-written, and created as described below in the section on Columns.\nYou can use the assignment operator from base R to create a new column. Below, the new column bmi (Body Mass Index) is created, and for each row the new value is result of a mathematical operation on the row’s value in the wt_kg and ht_cm columns.\n\n# create new \"bmi\" column using base R syntax\nlinelist$bmi <- linelist$wt_kg / (linelist$ht_cm/100)^2\n\nHowever, in this handbook, we emphasize a different approach to defining columns, which uses the function mutate() from the dplyr package and piping with the pipe operator (%>%). The syntax is easier to read and there are other advantages explained in the page on Cleaning data and core functions. You can read more about piping in the Piping section below.\n\n# create new \"bmi\" column using dplyr syntax\nlinelist <- linelist %>% \n mutate(bmi = wt_kg / (ht_cm/100)^2)\n\n\n\n\nObject structure\nObjects can be a single piece of data (e.g. my_number <- 24), or they can consist of structured data.\nThe graphic below is borrowed from this online R tutorial. It shows some common data structures and their names. Not included in this image is spatial data, which is discussed in the GIS basics page.\n\n\n\n\n\n\n\n\n\nIn epidemiology (and particularly field epidemiology), you will most commonly encounter data frames and vectors:\n\n\n\n\n\n\n\n\nCommon structure\nExplanation\nExample\n\n\n\nVectors | A container for a sequence of singular objects, all of the same class (e.g. numeric, character). | “Variables” (columns) in data frames are vectors (e.g. the column age_years). |\n\n\n\n\n\n\n\n\nData Frames\nVectors (e.g. columns) that are bound together that all have the same number of rows.\nlinelist is a data frame.\n\n\n\nNote that to create a vector that “stands alone” (is not part of a data frame) the function c() is used to combine the different elements. For example, if creating a vector of colors plot’s color scale: vector_of_colors <- c(\"blue\", \"red2\", \"orange\", \"grey\")\n\n\n\nObject classes\nAll the objects stored in R have a class which tells R how to handle the object. There are many possible classes, but common ones include:\n\n\n\nClass\nExplanation\nExamples\n\n\n\n\n\nCharacter\nThese are text/words/sentences “within quotation marks”. Math cannot be done on these objects.\n“Character objects are in quotation marks”\n\n\n\nInteger\nNumbers that are whole only (no decimals)\n-5, 14, or 2000\n\n\n\nNumeric\nThese are numbers and can include decimals. If within quotation marks they will be considered character class.\n23.1 or 14\n\n\n\nFactor\nThese are vectors that have a specified order or hierarchy of values\nAn variable of economic status with ordered values\n\n\n\nDate\nOnce R is told that certain data are Dates, these data can be manipulated and displayed in special ways. See the page on Working with dates for more information.\n2018-04-12 or 15/3/1954 or Wed 4 Jan 1980\n\n\n\nLogical\nValues must be one of the two special values TRUE or FALSE (note these are not “TRUE” and “FALSE” in quotation marks)\nTRUE or FALSE\n\n\n\ndata.frame\nA data frame is how R stores a typical dataset. It consists of vectors (columns) of data bound together, that all have the same number of observations (rows).\nThe example AJS dataset named linelist_raw contains 68 variables with 300 observations (rows) each.\n\n\n\ntibble\ntibbles are a variation on data frame, the main operational difference being that they print more nicely to the console (display first 10 rows and only columns that fit on the screen)\nAny data frame, list, or matrix can be converted to a tibble with as_tibble()\n\n\n\nlist\nA list is like vector, but holds other objects that can be other different classes\nA list could hold a single number, and a data frame, and a vector, and even another list within it!\n\n\n\n\nYou can test the class of an object by providing its name to the function class(). Note: you can reference a specific column within a dataset using the $ notation to separate the name of the dataset and the name of the column.\n\nclass(linelist) # class should be a data frame or tibble\n\n[1] \"data.frame\"\n\nclass(linelist$age) # class should be numeric\n\n[1] \"numeric\"\n\nclass(linelist$gender) # class should be character\n\n[1] \"character\"\n\n\nSometimes, a column will be converted to a different class automatically by R. Watch out for this! For example, if you have a vector or column of numbers, but a character value is inserted… the entire column will change to class character.\n\nnum_vector <- c(1, 2, 3, 4, 5) # define vector as all numbers\nclass(num_vector) # vector is numeric class\n\n[1] \"numeric\"\n\nnum_vector[3] <- \"three\" # convert the third element to a character\nclass(num_vector) # vector is now character class\n\n[1] \"character\"\n\n\nOne common example of this is when manipulating a data frame in order to print a table - if you make a total row and try to paste/glue together percents in the same cell as numbers (e.g. 23 (40%)), the entire numeric column above will convert to character and can no longer be used for mathematical calculations.Sometimes, you will need to convert objects or columns to another class.\n\n\n\nFunction\nAction\n\n\n\n\nas.character()\nConverts to character class\n\n\nas.numeric()\nConverts to numeric class\n\n\nas.integer()\nConverts to integer class\n\n\nas.Date()\nConverts to Date class - Note: see section on dates for details\n\n\nfactor()\nConverts to factor - Note: re-defining order of value levels requires extra arguments\n\n\n\nLikewise, there are base R functions to check whether an object IS of a specific class, such as is.numeric(), is.character(), is.double(), is.factor(), is.integer()\nHere is more online material on classes and data structures in R.\n\n\n\nColumns/Variables ($)\nA column in a data frame is technically a “vector” (see table above) - a series of values that must all be the same class (either character, numeric, logical, etc).\nA vector can exist independent of a data frame, for example a vector of column names that you want to include as explanatory variables in a model. To create a “stand alone” vector, use the c() function as below:\n\n# define the stand-alone vector of character values\nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n# print the values in this named vector\nexplanatory_vars\n\n[1] \"gender\" \"fever\" \"chills\" \"cough\" \"aches\" \"vomit\" \n\n\nColumns in a data frame are also vectors and can be called, referenced, extracted, or created using the $ symbol. The $ symbol connects the name of the column to the name of its data frame. In this handbook, we try to use the word “column” instead of “variable”.\n\n# Retrieve the length of the vector age_years\nlength(linelist$age) # (age is a column in the linelist data frame)\n\nBy typing the name of the data frame followed by $ you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes!\n\n\n\n\n\n\n\n\n\nADVANCED TIP: Some more complex objects (e.g. a list, or an epicontacts object) may have multiple levels which can be accessed through multiple dollar signs. For example epicontacts$linelist$date_onset\n\n\n\nAccess/index with brackets ([ ])\nYou may need to view parts of objects, also called “indexing”, which is often done using the square brackets [ ]. Using $ on a data frame to access a column is also a type of indexing.\n\nmy_vector <- c(\"a\", \"b\", \"c\", \"d\", \"e\", \"f\") # define the vector\nmy_vector[5] # print the 5th element\n\n[1] \"e\"\n\n\nSquare brackets also work to return specific parts of an returned output, such as the output of a summary() function:\n\n# All of the summary\nsummary(linelist$age)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.07 23.00 84.00 86 \n\n# Just the second element of the summary, with name (using only single brackets)\nsummary(linelist$age)[2]\n\n1st Qu. \n 6 \n\n# Just the second element, without name (using double brackets)\nsummary(linelist$age)[[2]]\n\n[1] 6\n\n# Extract an element by name, without showing the name\nsummary(linelist$age)[[\"Median\"]]\n\n[1] 13\n\n\nBrackets also work on data frames to view specific rows and columns. You can do this using the syntax data frame[rows, columns]:\n\n# View a specific row (2) from dataset, with all columns (don't forget the comma!)\nlinelist[2,]\n\n# View all rows, but just one column\nlinelist[, \"date_onset\"]\n\n# View values from row 2 and columns 5 through 10\nlinelist[2, 5:10] \n\n# View values from row 2 and columns 5 through 10 and 18\nlinelist[2, c(5:10, 18)] \n\n# View rows 2 through 20, and specific columns\nlinelist[2:20, c(\"date_onset\", \"outcome\", \"age\")]\n\n# View rows and columns based on criteria\n# *** Note the data frame must still be named in the criteria!\nlinelist[linelist$age > 25 , c(\"date_onset\", \"outcome\", \"age\")]\n\n# Use View() to see the outputs in the RStudio Viewer pane (easier to read) \n# *** Note the capital \"V\" in View() function\nView(linelist[2:20, \"date_onset\"])\n\n# Save as a new object\nnew_table <- linelist[2:20, c(\"date_onset\")] \n\nNote that you can also achieve the above row/column indexing on data frames and tibbles using dplyr syntax (functions filter() for rows, and select() for columns). Read more about these core functions in the Cleaning data and core functions page.\nTo filter based on “row number”, you can use the dplyr function row_number() with open parentheses as part of a logical filtering statement. Often you will use the %in% operator and a range of numbers as part of that logical statement, as shown below. To see the first N rows, you can also use the special dplyr function head(). Note, there is a function head() from base R, but this is overwritten by the dplyr function when you load tidyverse.\n\n# View first 100 rows\nlinelist %>% head(100)\n\n# Show row 5 only\nlinelist %>% filter(row_number() == 5)\n\n# View rows 2 through 20, and three specific columns (note no quotes necessary on column names)\nlinelist %>% \n filter(row_number() %in% 2:20) %>% \n select(date_onset, outcome, age)\n\nWhen indexing an object of class list, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list. Brackets can also be written after one another, as demonstrated below.\nThis visual explanation of lists indexing, with pepper shakers is humorous and helpful.\n\n# define demo list\nmy_list <- list(\n # First element in the list is a character vector\n hospitals = c(\"Central\", \"Empire\", \"Santa Anna\"),\n \n # second element in the list is a data frame of addresses\n addresses = data.frame(\n street = c(\"145 Medical Way\", \"1048 Brown Ave\", \"999 El Camino\"),\n city = c(\"Andover\", \"Hamilton\", \"El Paso\")\n )\n )\n\nHere is how the list looks when printed to the console. See how there are two named elements:\n\nhospitals, a character vector\n\naddresses, a data frame of addresses\n\n\nmy_list\n\n$hospitals\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\n$addresses\n street city\n1 145 Medical Way Andover\n2 1048 Brown Ave Hamilton\n3 999 El Camino El Paso\n\n\nNow we extract, using various methods:\n\nmy_list[1] # this returns the element in class \"list\" - the element name is still displayed\n\n$hospitals\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\nmy_list[[1]] # this returns only the (unnamed) character vector\n\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\nmy_list[[\"hospitals\"]] # you can also index by name of the list element\n\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\nmy_list[[1]][3] # this returns the third element of the \"hospitals\" character vector\n\n[1] \"Santa Anna\"\n\nmy_list[[2]][1] # This returns the first column (\"street\") of the address data frame\n\n street\n1 145 Medical Way\n2 1048 Brown Ave\n3 999 El Camino\n\n\n\n\n\nRemove objects\nYou can remove individual objects from your R environment by putting the name in the rm() function (no quote marks):\n\nrm(object_name)\n\nYou can remove all objects (clear your workspace) by running:\n\nrm(list = ls(all = TRUE))", "crumbs": [ "Basics", "3  R Basics" @@ -251,8 +251,8 @@ "objectID": "new_pages/basics.html#piping", "href": "new_pages/basics.html#piping", "title": "3  R Basics", - "section": "8.1 Piping (%>%)", - "text": "8.1 Piping (%>%)\nTwo general approaches to working with objects are:\n\nPipes/tidyverse - pipes send an object from function to function - emphasis is on the action, not the object\n\nDefine intermediate objects - an object is re-defined again and again - emphasis is on the object\n\n\n\nPipes\nSimply explained, the pipe operator (%>%) passes an intermediate output from one function to the next.\nYou can think of it as saying “then”. Many functions can be linked together with %>%.\n\nPiping emphasizes a sequence of actions, not the object the actions are being performed on\n\nPipes are best when a sequence of actions must be performed on one object\n\nPipes come from the package magrittr, which is automatically included in packages dplyr and tidyverse\nPipes can make code more clean and easier to read, more intuitive\n\nRead more on this approach in the tidyverse style guide\nHere is a fake example for comparison, using fictional functions to “bake a cake”. First, the pipe method:\n\n# A fake example of how to bake a cake using piping syntax\n\ncake <- flour %>% # to define cake, start with flour, and then...\n add(eggs) %>% # add eggs\n add(oil) %>% # add oil\n add(water) %>% # add water\n mix_together( # mix together\n utensil = spoon,\n minutes = 2) %>% \n bake(degrees = 350, # bake\n system = \"fahrenheit\",\n minutes = 35) %>% \n let_cool() # let it cool down\n\nHere is another link describing the utility of pipes.\nPiping is not a base function. To use piping, the magrittr package must be installed and loaded (this is typically done by loading tidyverse or dplyr package which include it). You can read more about piping in the magrittr documentation.\nNote that just like other R commands, pipes can be used to just display the result, or to save/re-save an object, depending on whether the assignment operator <- is involved. See both below:\n\n# Create or overwrite object, defining as aggregate counts by age category (not printed)\nlinelist_summary <- linelist %>% \n count(age_cat)\n\n\n# Print the table of counts in the console, but don't save it\nlinelist %>% \n count(age_cat)\n\n age_cat n\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 <NA> 86\n\n\n%<>%\nThis is an “assignment pipe” from the magrittr package, which pipes an object forward and also re-defines the object. It must be the first pipe operator in the chain. It is shorthand. The below two commands are equivalent:\n\nlinelist <- linelist %>%\n filter(age > 50)\n\nlinelist %<>% filter(age > 50)\n\n\n\n\nDefine intermediate objects\nThis approach to changing objects/dataframes may be better if:\n\nYou need to manipulate multiple objects\n\nThere are intermediate steps that are meaningful and deserve separate object names\n\nRisks:\n\nCreating new objects for each step means creating lots of objects. If you use the wrong one you might not realize it!\n\nNaming all the objects can be confusing\n\nErrors may not be easily detectable\n\nEither name each intermediate object, or overwrite the original, or combine all the functions together. All come with their own risks.\nBelow is the same fake “cake” example as above, but using this style:\n\n# a fake example of how to bake a cake using this method (defining intermediate objects)\nbatter_1 <- left_join(flour, eggs)\nbatter_2 <- left_join(batter_1, oil)\nbatter_3 <- left_join(batter_2, water)\n\nbatter_4 <- mix_together(object = batter_3, utensil = spoon, minutes = 2)\n\ncake <- bake(batter_4, degrees = 350, system = \"fahrenheit\", minutes = 35)\n\ncake <- let_cool(cake)\n\nCombine all functions together - this is difficult to read:\n\n# an example of combining/nesting mutliple functions together - difficult to read\ncake <- let_cool(bake(mix_together(batter_3, utensil = spoon, minutes = 2), degrees = 350, system = \"fahrenheit\", minutes = 35))", + "section": "3.11 Piping (%>%)", + "text": "3.11 Piping (%>%)\nTwo general approaches to working with objects are:\n\nPipes/tidyverse - pipes send an object from function to function - emphasis is on the action, not the object.\n\nDefine intermediate objects - an object is re-defined again and again - emphasis is on the object.\n\n\n\nPipes\nSimply explained, the pipe operator passes an intermediate output from one function to the next.\nYou can think of it as saying “and then”. Many functions can be linked together with %>%.\n\nPiping emphasizes a sequence of actions, not the object the actions are being performed on.\n\nPipes are best when a sequence of actions must be performed on one object.\n\nPipes can make code more clean and easier to read, more intuitive.\n\nPipe operators were first introduced through the magrittr package, which is part of tidyverse, and were specified as %>%. In R 4.1.0, they introduced a base R pipe which is specified through |>. The behaviour of the two pipes is the same, and they can be used somewhat interchangeably. However, there are a few key differences.\n\nThe %>% pipe allows you to pass multiple arguments.\nThe %>% pipe lets you drop parentheses when calling a function with no other arguments (i.e. drop vs drop()).\nThe %>% pipe allows you to start a pipe with . to create a function in your linking of code.\n\nFor these reasons, we recommend the magrittr pipe, %>%, over the base R pipe, |>.\nTo read more about the differences between base R and tidyverse (magrittr) pipes, see this blog post. For more information on the tidyverse approach, please see this style guide.\nHere is a fake example for comparison, using fictional functions to “bake a cake”. First, the pipe method:\n\n# A fake example of how to bake a cake using piping syntax\n\ncake <- flour %>% # to define cake, start with flour, and then...\n add(eggs) %>% # add eggs\n add(oil) %>% # add oil\n add(water) %>% # add water\n mix_together( # mix together\n utensil = spoon,\n minutes = 2) %>% \n bake(degrees = 350, # bake\n system = \"fahrenheit\",\n minutes = 35) %>% \n let_cool() # let it cool down\n\nNote that just like other R commands, pipes can be used to just display the result, or to save/re-save an object, depending on whether the assignment operator <- is involved. See both below:\n\n# Create or overwrite object, defining as aggregate counts by age category (not printed)\nlinelist_summary <- linelist %>% \n count(age_cat)\n\n\n# Print the table of counts in the console, but don't save it\nlinelist %>% \n count(age_cat)\n\n age_cat n\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 <NA> 86\n\n\n%<>%\nThis is an “assignment pipe” from the magrittr package, which pipes an object forward and also re-defines the object. It must be the first pipe operator in the chain. It is shorthand. The below two commands are equivalent:\n\nlinelist <- linelist %>%\n filter(age > 50)\n\nlinelist %<>% filter(age > 50)\n\n\n\n\nDefine intermediate objects\nThis approach to changing objects/data frames may be better if:\n\nYou need to manipulate multiple objects\n\nThere are intermediate steps that are meaningful and deserve separate object names\n\nRisks:\n\nCreating new objects for each step means creating lots of objects. If you use the wrong one you might not realize it!\n\nNaming all the objects can be confusing.\n\nErrors may not be easily detectable.\n\nEither name each intermediate object, or overwrite the original, or combine all the functions together. All come with their own risks.\nBelow is the same fake “cake” example as above, but using this style:\n\n# a fake example of how to bake a cake using this method (defining intermediate objects)\nbatter_1 <- left_join(flour, eggs)\nbatter_2 <- left_join(batter_1, oil)\nbatter_3 <- left_join(batter_2, water)\n\nbatter_4 <- mix_together(object = batter_3, utensil = spoon, minutes = 2)\n\ncake <- bake(batter_4, degrees = 350, system = \"fahrenheit\", minutes = 35)\n\ncake <- let_cool(cake)\n\nCombine all functions together - this is difficult to read:\n\n# an example of combining/nesting mutliple functions together - difficult to read\ncake <- let_cool(bake(mix_together(batter_3, utensil = spoon, minutes = 2), degrees = 350, system = \"fahrenheit\", minutes = 35))", "crumbs": [ "Basics", "3  R Basics" @@ -262,8 +262,8 @@ "objectID": "new_pages/basics.html#operators", "href": "new_pages/basics.html#operators", "title": "3  R Basics", - "section": "8.2 Key operators and functions", - "text": "8.2 Key operators and functions\nThis section details operators in R, such as:\n\nDefinitional operators\n\nRelational operators (less than, equal too..)\n\nLogical operators (and, or…)\n\nHandling missing values\n\nMathematical operators and functions (+/-, >, sum(), median(), …)\n\nThe %in% operator\n\n\n\nAssignment operators\n<-\nThe basic assignment operator in R is <-. Such that object_name <- value.\nThis assignment operator can also be written as =. We advise use of <- for general R use.\nWe also advise surrounding such operators with spaces, for readability.\n<<-\nIf Writing functions, or using R in an interactive way with sourced scripts, then you may need to use this assignment operator <<- (from base R). This operator is used to define an object in a higher ‘parent’ R Environment. See this online reference.\n%<>%\nThis is an “assignment pipe” from the magrittr package, which pipes an object forward and also re-defines the object. It must be the first pipe operator in the chain. It is shorthand, as shown below in two equivalent examples:\n\nlinelist <- linelist %>% \n mutate(age_months = age_years * 12)\n\nThe above is equivalent to the below:\n\nlinelist %<>% mutate(age_months = age_years * 12)\n\n%<+%\nThis is used to add data to phylogenetic trees with the ggtree package. See the page on Phylogenetic trees or this online resource book.\n\n\n\nRelational and logical operators\nRelational operators compare values and are often used when defining new variables and subsets of datasets. Here are the common relational operators in R:\n\n\n\n9 Meaning\nEqual to\n10 Operator\n==\n11 Example\n\"A\" == \"a\"\n12 Example Result\nFALSE (because R is case sensitive) Note that == (double equals) is different from = (single equals), which acts like the assignment operator <-\n\n\n\n\n\n\n\n\n\n\nNot equal to\n!=\n2 != 0\nTRUE\n\n\n\nGreater than\n>\n4 > 2\nTRUE\n\n\n\nLess than\n<\n4 < 2\nFALSE\n\n\n\nGreater than or equal to\n>=\n6 >= 4\nTRUE\n\n\n\nLess than or equal to\n<=\n6 <= 4\nFALSE\n\n\n\nValue is missing\nis.na()\nis.na(7)\nFALSE (see page on Missing data)\n\n\n\nValue is not missing\n!is.na()\n!is.na(7)\nTRUE\n\n\n\n\nLogical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses ( ) for grouping and order of application.\n\n\n\n\n\n\n\nMeaning\nOperator\n\n\n\n\nAND\n&\n\n\nOR\n| (vertical bar)\n\n\nParentheses\n( ) Used to group criteria together and clarify order of operations\n\n\n\nFor example, below, we have a linelist with two variables we want to use to create our case definition, hep_e_rdt, a test result and other_cases_in_hh, which will tell us if there are other cases in the household. The command below uses the function case_when() to create the new variable case_def such that:\n\nlinelist_cleaned <- linelist %>%\n mutate(case_def = case_when(\n is.na(rdt_result) & is.na(other_case_in_home) ~ NA_character_,\n rdt_result == \"Positive\" ~ \"Confirmed\",\n rdt_result != \"Positive\" & other_cases_in_home == \"Yes\" ~ \"Probable\",\n TRUE ~ \"Suspected\"\n ))\n\n\n\n\n\n\n\n\nCriteria in example above\nResulting value in new variable “case_def”\n\n\n\n\nIf the value for variables rdt_result and other_cases_in_home are missing\nNA (missing)\n\n\nIf the value in rdt_result is “Positive”\n“Confirmed”\n\n\nIf the value in rdt_result is NOT “Positive” AND the value in other_cases_in_home is “Yes”\n“Probable”\n\n\nIf one of the above criteria are not met\n“Suspected”\n\n\n\nNote that R is case-sensitive, so “Positive” is different than “positive”…\n\n\n\nMissing values\nIn R, missing values are represented by the special value NA (a “reserved” value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, “Missing”, or .), you may want to re-code those values to NA. How to do this is addressed in the Import and export page.\nTo test whether a value is NA, use the special function is.na(), which returns TRUE or FALSE.\n\nrdt_result <- c(\"Positive\", \"Suspected\", \"Positive\", NA) # two positive cases, one suspected, and one unknown\nis.na(rdt_result) # Tests whether the value of rdt_result is NA\n\n[1] FALSE FALSE FALSE TRUE\n\n\nRead more about missing, infinite, NULL, and impossible values in the page on Missing data. Learn how to convert missing values when importing data in the page on Import and export.\n\n\n\nMathematics and statistics\nAll the operators and functions in this page are automatically available using base R.\n\nMathematical operators\nThese are often used to perform addition, division, to create new columns, etc. Below are common mathematical operators in R. Whether you put spaces around the operators is not important.\n\n\n\nPurpose\nExample in R\n\n\n\n\naddition\n2 + 3\n\n\nsubtraction\n2 - 3\n\n\nmultiplication\n2 * 3\n\n\ndivision\n30 / 5\n\n\nexponent\n2^3\n\n\norder of operations\n( )\n\n\n\n\n\nMathematical functions\n\n\n\nPurpose\nFunction\n\n\n\n\nrounding\nround(x, digits = n)\n\n\nrounding\njanitor::round_half_up(x, digits = n)\n\n\nceiling (round up)\nceiling(x)\n\n\nfloor (round down)\nfloor(x)\n\n\nabsolute value\nabs(x)\n\n\nsquare root\nsqrt(x)\n\n\nexponent\nexponent(x)\n\n\nnatural logarithm\nlog(x)\n\n\nlog base 10\nlog10(x)\n\n\nlog base 2\nlog2(x)\n\n\n\nNote: for round() the digits = specifies the number of decimal placed. Use signif() to round to a number of significant figures.\n\n\nScientific notation\nThe likelihood of scientific notation being used depends on the value of the scipen option.\nFrom the documentation of ?options: scipen is a penalty to be applied when deciding to print numeric values in fixed or exponential notation. Positive values bias towards fixed and negative towards scientific notation: fixed notation will be preferred unless it is more than ‘scipen’ digits wider.\nIf it is set to a low number (e.g. 0) it will be “turned on” always. To “turn off” scientific notation in your R session, set it to a very high number, for example:\n\n# turn off scientific notation\noptions(scipen=999)\n\n\n\nRounding\nDANGER: round() uses “banker’s rounding” which rounds up from a .5 only if the upper number is even. Use round_half_up() from janitor to consistently round halves up to the nearest whole number. See this explanation\n\n# use the appropriate rounding function for your work\nround(c(2.5, 3.5))\n\n[1] 2 4\n\njanitor::round_half_up(c(2.5, 3.5))\n\n[1] 3 4\n\n\n\n\nStatistical functions\nCAUTION: The functions below will by default include missing values in calculations. Missing values will result in an output of NA, unless the argument na.rm = TRUE is specified. This can be written shorthand as na.rm = T.\n\n\n\nObjective\nFunction\n\n\n\n\nmean (average)\nmean(x, na.rm=T)\n\n\nmedian\nmedian(x, na.rm=T)\n\n\nstandard deviation\nsd(x, na.rm=T)\n\n\nquantiles*\nquantile(x, probs)\n\n\nsum\nsum(x, na.rm=T)\n\n\nminimum value\nmin(x, na.rm=T)\n\n\nmaximum value\nmax(x, na.rm=T)\n\n\nrange of numeric values\nrange(x, na.rm=T)\n\n\nsummary**\nsummary(x)\n\n\n\nNotes:\n\n*quantile(): x is the numeric vector to examine, and probs = is a numeric vector with probabilities within 0 and 1.0, e.g c(0.5, 0.8, 0.85)\n**summary(): gives a summary on a numeric vector including mean, median, and common percentiles\n\nDANGER: If providing a vector of numbers to one of the above functions, be sure to wrap the numbers within c() .\n\n# If supplying raw numbers to a function, wrap them in c()\nmean(1, 6, 12, 10, 5, 0) # !!! INCORRECT !!! \n\n[1] 1\n\nmean(c(1, 6, 12, 10, 5, 0)) # CORRECT\n\n[1] 5.666667\n\n\n\n\nOther useful functions\n\n\n\n\n\n\n\n\nObjective\nFunction\nExample\n\n\n\n\ncreate a sequence\nseq(from, to, by)\nseq(1, 10, 2)\n\n\nrepeat x, n times\nrep(x, ntimes)\nrep(1:3, 2) or rep(c(\"a\", \"b\", \"c\"), 3)\n\n\nsubdivide a numeric vector\ncut(x, n)\ncut(linelist$age, 5)\n\n\ntake a random sample\nsample(x, size)\nsample(linelist$id, size = 5, replace = TRUE)\n\n\n\n\n\n\n\n%in%\nA very useful operator for matching values, and for quickly assessing if a value is within a vector or dataframe.\n\nmy_vector <- c(\"a\", \"b\", \"c\", \"d\")\n\n\n\"a\" %in% my_vector\n\n[1] TRUE\n\n\"h\" %in% my_vector\n\n[1] FALSE\n\n\nTo ask if a value is not %in% a vector, put an exclamation mark (!) in front of the logic statement:\n\n# to negate, put an exclamation in front\n!\"a\" %in% my_vector\n\n[1] FALSE\n\n!\"h\" %in% my_vector\n\n[1] TRUE\n\n\n%in% is very useful when using the dplyr function case_when(). You can define a vector previously, and then reference it later. For example:\n\naffirmative <- c(\"1\", \"Yes\", \"YES\", \"yes\", \"y\", \"Y\", \"oui\", \"Oui\", \"Si\")\n\nlinelist <- linelist %>% \n mutate(child_hospitaled = case_when(\n hospitalized %in% affirmative & age < 18 ~ \"Hospitalized Child\",\n TRUE ~ \"Not\"))\n\nNote: If you want to detect a partial string, perhaps using str_detect() from stringr, it will not accept a character vector like c(\"1\", \"Yes\", \"yes\", \"y\"). Instead, it must be given a regular expression - one condensed string with OR bars, such as “1|Yes|yes|y”. For example, str_detect(hospitalized, \"1|Yes|yes|y\"). See the page on Characters and strings for more information.\nYou can convert a character vector to a named regular expression with this command:\n\naffirmative <- c(\"1\", \"Yes\", \"YES\", \"yes\", \"y\", \"Y\", \"oui\", \"Oui\", \"Si\")\naffirmative\n\n[1] \"1\" \"Yes\" \"YES\" \"yes\" \"y\" \"Y\" \"oui\" \"Oui\" \"Si\" \n\n# condense to \naffirmative_str_search <- paste0(affirmative, collapse = \"|\") # option with base R\naffirmative_str_search <- str_c(affirmative, collapse = \"|\") # option with stringr package\n\naffirmative_str_search\n\n[1] \"1|Yes|YES|yes|y|Y|oui|Oui|Si\"", + "section": "3.12 Key operators and functions", + "text": "3.12 Key operators and functions\nThis section details operators in R, such as:\n\nDefinitional operators.\n\nRelational operators (less than, equal too..).\n\nLogical operators (and, or…).\n\nHandling missing values.\n\nMathematical operators and functions (+/-, >, sum(), median(), …).\n\nThe %in% operator.\n\n\n\nAssignment operators\n<-\nThe basic assignment operator in R is <-. Such that object_name <- value.\nThis assignment operator can also be written as =. We advise use of <- for general R use. We also advise surrounding such operators with spaces, for readability.\n<<-\nIf Writing functions, or using R in an interactive way with sourced scripts, then you may need to use this assignment operator <<- (from base R). This operator is used to define an object in a higher ‘parent’ R Environment. See this online reference.\n%<>%\nThis is an “assignment pipe” from the magrittr package, which pipes an object forward and also re-defines the object. It must be the first pipe operator in the chain. It is shorthand, as shown below in two equivalent examples:\n\nlinelist <- linelist %>% \n mutate(age_months = age_years * 12)\n\nThe above is equivalent to the below:\n\nlinelist %<>% mutate(age_months = age_years * 12)\n\n%<+%\nThis is used to add data to phylogenetic trees with the ggtree package. See the page on Phylogenetic trees or this online resource book.\n\n\n\nRelational and logical operators\nRelational operators compare values and are often used when defining new variables and subsets of datasets. Here are the common relational operators in R:\n\n\n\nMeaning\nOperator\nExample\nExample Result\n\n\n\n\nEqual to\n==\n\"A\" == \"a\"\nFALSE (because R is case sensitive) Note that == (double equals) is different from = (single equals), which acts like the assignment operator <-\n\n\nNot equal to\n!=\n2 != 0\nTRUE\n\n\nGreater than\n>\n4 > 2\nTRUE\n\n\nLess than\n<\n4 < 2\nFALSE\n\n\nGreater than or equal to\n>=\n6 >= 4\nTRUE\n\n\nLess than or equal to\n<=\n6 <= 4\nFALSE\n\n\nValue is missing\nis.na()\nis.na(7)\nFALSE (see page on Missing data)\n\n\nValue is not missing\n!is.na()\n!is.na(7)\nTRUE\n\n\n\nLogical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses ( ) for grouping and order of application.\n\n\n\n\n\n\n\nMeaning\nOperator\n\n\n\n\nAND\n&\n\n\nOR\n| (vertical bar)\n\n\nParentheses\n( ) Used to group criteria together and clarify order of operations\n\n\n\nFor example, below, we have a linelist with two variables we want to use to create our case definition, hep_e_rdt, a test result and other_cases_in_hh, which will tell us if there are other cases in the household. The command below uses the function case_when() to create the new variable case_def such that:\n\nlinelist_cleaned <- linelist %>%\n mutate(case_def = case_when(\n is.na(rdt_result) & is.na(other_case_in_home) ~ NA_character_,\n rdt_result == \"Positive\" ~ \"Confirmed\",\n rdt_result != \"Positive\" & other_cases_in_home == \"Yes\" ~ \"Probable\",\n TRUE ~ \"Suspected\"\n ))\n\n\n\n\n\n\n\n\nCriteria in example above\nResulting value in new variable “case_def”\n\n\n\n\nIf the value for variables rdt_result and other_cases_in_home are missing\nNA (missing)\n\n\nIf the value in rdt_result is “Positive”\n“Confirmed”\n\n\nIf the value in rdt_result is NOT “Positive” AND the value in other_cases_in_home is “Yes”\n“Probable”\n\n\nIf one of the above criteria are not met\n“Suspected”\n\n\n\nNote that R is case-sensitive, so “Positive” is different than “positive”.\n\n\n\nMissing values\nIn R, missing values are represented by the special value NA (a “reserved” value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, “Missing”), you may want to re-code those values to NA. How to do this is addressed in the Import and export page.\nTo test whether a value is NA, use the special function is.na(), which returns TRUE or FALSE.\n\nrdt_result <- c(\"Positive\", \"Suspected\", \"Positive\", NA) # two positive cases, one suspected, and one unknown\nis.na(rdt_result) # Tests whether the value of rdt_result is NA\n\n[1] FALSE FALSE FALSE TRUE\n\n\nRead more about missing, infinite, NULL, and impossible values in the page on Missing data. Learn how to convert missing values when importing data in the page on Import and export.\n\n\n\nMathematics and statistics\nAll the operators and functions in this page are automatically available using base R.\n\nMathematical operators\nThese are often used to perform addition, division, to create new columns, etc. Below are common mathematical operators in R. Whether you put spaces around the operators is not important.\n\n\n\nPurpose\nExample in R\n\n\n\n\naddition\n2 + 3\n\n\nsubtraction\n2 - 3\n\n\nmultiplication\n2 * 3\n\n\ndivision\n30 / 5\n\n\nexponent\n2^3\n\n\norder of operations\n( )\n\n\n\n\n\nMathematical functions\n\n\n\nPurpose\nFunction\n\n\n\n\nrounding\nround(x, digits = n)\n\n\nrounding\njanitor::round_half_up(x, digits = n)\n\n\nceiling (round up)\nceiling(x)\n\n\nfloor (round down)\nfloor(x)\n\n\nabsolute value\nabs(x)\n\n\nsquare root\nsqrt(x)\n\n\nexponent\nexponent(x)\n\n\nnatural logarithm\nlog(x)\n\n\nlog base 10\nlog10(x)\n\n\nlog base 2\nlog2(x)\n\n\n\nNote: for round() the digits = specifies the number of decimal placed. Use signif() to round to a number of significant figures.\n\n\nScientific notation\nThe likelihood of scientific notation being used depends on the value of the scipen option.\nFrom the documentation of ?options: scipen is a penalty to be applied when deciding to print numeric values in fixed or exponential notation. Positive values bias towards fixed and negative towards scientific notation: fixed notation will be preferred unless it is more than ‘scipen’ digits wider.\nIf it is set to a low number (e.g. 0) it will be “turned on” always. To “turn off” scientific notation in your R session, set it to a very high number, for example:\n\n# turn off scientific notation\noptions(scipen = 999)\n\n\n\nRounding\nDANGER: round() uses “banker’s rounding” which rounds up from a .5 only if the upper number is even. Use round_half_up() from janitor to consistently round halves up to the nearest whole number. See this explanation\n\n# use the appropriate rounding function for your work\nround(c(2.5, 3.5))\n\n[1] 2 4\n\njanitor::round_half_up(c(2.5, 3.5))\n\n[1] 3 4\n\n\nFor rounding from proportion to percentages, you can use the function percent() from the scales package.\n\nscales::percent(c(0.25, 0.35), accuracy = 0.1)\n\n[1] \"25.0%\" \"35.0%\"\n\n\n\n\nStatistical functions\nCAUTION: The functions below will by default include missing values in calculations. Missing values will result in an output of NA, unless the argument na.rm = TRUE is specified. This can be written shorthand as na.rm = T.\n\n\n\nObjective\nFunction\n\n\n\n\nmean (average)\nmean(x, na.rm = T)\n\n\nmedian\nmedian(x, na.rm= T)\n\n\nstandard deviation\nsd(x, na.rm = T)\n\n\nquantiles*\nquantile(x, probs)\n\n\nsum\nsum(x, na.rm = T)\n\n\nminimum value\nmin(x, na.rm = T)\n\n\nmaximum value\nmax(x, na.rm = T)\n\n\nrange of numeric values\nrange(x, na.rm = T)\n\n\nsummary**\nsummary(x)\n\n\n\nNotes:\n\n*quantile(): x is the numeric vector to examine, and probs = is a numeric vector with probabilities within 0 and 1.0, e.g c(0.5, 0.8, 0.85).\n**summary(): gives a summary on a numeric vector including mean, median, and common percentiles.\n\nDANGER: If providing a vector of numbers to one of the above functions, be sure to wrap the numbers within c() .\n\n# If supplying raw numbers to a function, wrap them in c()\nmean(1, 6, 12, 10, 5, 0) # !!! INCORRECT !!! \n\n[1] 1\n\nmean(c(1, 6, 12, 10, 5, 0)) # CORRECT\n\n[1] 5.666667\n\n\n\n\nOther useful functions\n\n\n\n\n\n\n\n\nObjective\nFunction\nExample\n\n\n\n\ncreate a sequence\nseq(from, to, by)\nseq(1, 10, 2)\n\n\nrepeat x, n times\nrep(x, ntimes)\nrep(1:3, 2) or rep(c(\"a\", \"b\", \"c\"), 3)\n\n\nsubdivide a numeric vector\ncut(x, n)\ncut(linelist$age, 5)\n\n\ntake a random sample\nsample(x, size)\nsample(linelist$id, size = 5, replace = TRUE)\n\n\n\n\n\n\n\n%in%\nA very useful operator for matching values, and for quickly assessing if a value is within a vector or data frame.\n\nmy_vector <- c(\"a\", \"b\", \"c\", \"d\")\n\n\n\"a\" %in% my_vector\n\n[1] TRUE\n\n\"h\" %in% my_vector\n\n[1] FALSE\n\n\nTo ask if a value is not %in% a vector, put an exclamation mark (!) in front of the logic statement:\n\n# to negate, put an exclamation in front\n!\"a\" %in% my_vector\n\n[1] FALSE\n\n!\"h\" %in% my_vector\n\n[1] TRUE\n\n\n%in% is very useful when using the dplyr function case_when(). You can define a vector previously, and then reference it later. For example:\n\naffirmative <- c(\"1\", \"Yes\", \"YES\", \"yes\", \"y\", \"Y\", \"oui\", \"Oui\", \"Si\")\n\nlinelist <- linelist %>% \n mutate(child_hospitaled = case_when(\n hospitalized %in% affirmative & age < 18 ~ \"Hospitalized Child\",\n TRUE ~ \"Not\"))\n\nNote: If you want to detect a partial string, perhaps using str_detect() from stringr, it will not accept a character vector like c(\"1\", \"Yes\", \"yes\", \"y\"). Instead, it must be given a regular expression - one condensed string with OR bars, such as “1|Yes|yes|y”. For example, str_detect(hospitalized, \"1|Yes|yes|y\"). See the page on Characters and strings for more information.\nYou can convert a character vector to a named regular expression with this command:\n\naffirmative <- c(\"1\", \"Yes\", \"YES\", \"yes\", \"y\", \"Y\", \"oui\", \"Oui\", \"Si\")\naffirmative\n\n[1] \"1\" \"Yes\" \"YES\" \"yes\" \"y\" \"Y\" \"oui\" \"Oui\" \"Si\" \n\n# condense to \naffirmative_str_search <- paste0(affirmative, collapse = \"|\") # option with base R\naffirmative_str_search <- str_c(affirmative, collapse = \"|\") # option with stringr package\n\naffirmative_str_search\n\n[1] \"1|Yes|YES|yes|y|Y|oui|Oui|Si\"", "crumbs": [ "Basics", "3  R Basics" @@ -273,8 +273,8 @@ "objectID": "new_pages/basics.html#errors-warnings", "href": "new_pages/basics.html#errors-warnings", "title": "3  R Basics", - "section": "12.1 Errors & warnings", - "text": "12.1 Errors & warnings\nThis section explains:\n\nThe difference between errors and warnings\n\nGeneral syntax tips for writing R code\n\nCode assists\n\nCommon errors and warnings and troubleshooting tips can be found in the page on Errors and help.\n\n\nError versus Warning\nWhen a command is run, the R Console may show you warning or error messages in red text.\n\nA warning means that R has completed your command, but had to take additional steps or produced unusual output that you should be aware of.\nAn error means that R was not able to complete your command.\n\nLook for clues:\n\nThe error/warning message will often include a line number for the problem.\nIf an object “is unknown” or “not found”, perhaps you spelled it incorrectly, forgot to call a package with library(), or forgot to re-run your script after making changes.\n\nIf all else fails, copy the error message into Google along with some key terms - chances are that someone else has worked through this already!\n\n\n\nGeneral syntax tips\nA few things to remember when writing commands in R, to avoid errors and warnings:\n\nAlways close parentheses - tip: count the number of opening “(” and closing parentheses “)” for each code chunk\nAvoid spaces in column and object names. Use underscore ( _ ) or periods ( . ) instead\nKeep track of and remember to separate a function’s arguments with commas\nR is case-sensitive, meaning Variable_A is different from variable_A\n\n\n\n\nCode assists\nAny script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the right side of the script, to warn you.", + "section": "3.13 Errors & warnings", + "text": "3.13 Errors & warnings\nThis section explains:\n\nThe difference between errors and warnings.\n\nGeneral syntax tips for writing R code.\n\nCode assists.\n\nCommon errors and warnings and troubleshooting tips can be found in the page on Errors and help.\n\n\nError versus Warning\nWhen a command is run, the R Console may show you warning or error messages in red text.\n\nA warning means that R has completed your command, but had to take additional steps or produced unusual output that you should be aware of.\nAn error means that R was not able to complete your command.\n\nLook for clues:\n\nThe error/warning message will often include a line number for the problem.\nIf an object “is unknown” or “not found”, perhaps you spelled it incorrectly, forgot to call a package with library(), or forgot to re-run your script after making changes.\n\nIf all else fails, copy the error message into Google along with some key terms - chances are that someone else has worked through this already!\n\n\n\nGeneral syntax tips\nA few things to remember when writing commands in R, to avoid errors and warnings:\n\nAlways close parentheses - tip: count the number of opening “(” and closing parentheses “)” for each code chunk.\nAvoid spaces in column and object names. Use underscore ( _ ) or periods ( . ) instead.\nKeep track of and remember to separate a function’s arguments with commas.\nR is case-sensitive, meaning Variable_A is different from variable_A.\n\n\n\n\nCode assists\nAny script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the left hand side of the script, to warn you.", "crumbs": [ "Basics", "3  R Basics" @@ -296,7 +296,7 @@ "href": "new_pages/transition_to_R.html#from-excel", "title": "4  Transition to R", "section": "", - "text": "Benefits\nYou will find that using R offers immense benefits in time saved, more consistent and accurate analysis, reproducibility, shareability, and faster error-correction. Like any new software there is a learning “curve” of time you must invest to become familiar. The dividends will be significant and immense scope of new possibilities will open to you with R.\nExcel is a well-known software that can be easy for a beginner to use to produce simple analysis and visualizations with “point-and-click”. In comparison, it can take a couple weeks to become comfortable with R functions and interface. However, R has evolved in recent years to become much more friendly to beginners.\nMany Excel workflows rely on memory and on repetition - thus, there is much opportunity for error. Furthermore, generally the data cleaning, analysis methodology, and equations used are hidden from view. It can require substantial time for a new colleague to learn what an Excel workbook is doing and how to troubleshoot it. With R, all the steps are explicitly written in the script and can be easily viewed, edited, corrected, and applied to other datasets.\nTo begin your transition from Excel to R you must adjust your mindset in a few important ways:\n\n\nTidy data\nUse machine-readable “tidy” data instead of messy “human-readable” data. These are the three main requirements for “tidy” data, as explained in this tutorial on “tidy” data in R:\n\nEach variable must have its own column\n\nEach observation must have its own row\n\nEach value must have its own cell\n\nTo Excel users - think of the role that Excel “tables” play in standardizing data and making the format more predictable.\nAn example of “tidy” data would be the case linelist used throughout this handbook - each variable is contained within one column, each observation (one case) has it’s own row, and every value is in just one cell. Below you can view the first 50 rows of the linelist:\n\n\n\n\n\n\nThe main reason one encounters non-tidy data is because many Excel spreadsheets are designed to prioritize easy reading by humans, not easy reading by machines/software.\nTo help you see the difference, below are some fictional examples of non-tidy data that prioritize human-readability over machine-readability:\n\n\n\n\n\n\n\n\n\nProblems: In the spreadsheet above, there are merged cells which are not easily digested by R. Which row should be considered the “header” is not clear. A color-based dictionary is to the right side and cell values are represented by colors - which is also not easily interpreted by R (nor by humans with color-blindness!). Furthermore, different pieces of information are combined into one cell (multiple partner organizations working in one area, or the status “TBC” in the same cell as “Partner D”).\n\n\n\n\n\n\n\n\n\nProblems: In the spreadsheet above, there are numerous extra empty rows and columns within the dataset - this will cause cleaning headaches in R. Furthermore, the GPS coordinates are spread across two rows for a given treatment center. As a side note - the GPS coordinates are in two different formats!\n“Tidy” datasets may not be as readable to a human eye, but they make data cleaning and analysis much easier! Tidy data can be stored in various formats, for example “long” or “wide”“(see page on Pivoting data), but the principles above are still observed.\n\n\nFunctions\nThe R word “function” might be new, but the concept exists in Excel too as formulas. Formulas in Excel also require precise syntax (e.g. placement of semicolons and parentheses). All you need to do is learn a few new functions and how they work together in R.\n\n\nScripts\nInstead of clicking buttons and dragging cells you will be writing every step and procedure into a “script”. Excel users may be familiar with “VBA macros” which also employ a scripting approach.\nThe R script consists of step-by-step instructions. This allows any colleague to read the script and easily see the steps you took. This also helps de-bug errors or inaccurate calculations. See the R basics section on scripts for examples.\nHere is an example of an R script:\n\n\n\n\n\n\n\n\n\n\n\nExcel-to-R resources\nHere are some links to tutorials to help you transition to R from Excel:\n\nR vs. Excel\n\nRStudio course in R for Excel users\n\n\n\nR-Excel interaction\nR has robust ways to import Excel workbooks, work with the data, export/save Excel files, and work with the nuances of Excel sheets.\nIt is true that some of the more aesthetic Excel formatting can get lost in translation (e.g. italics, sideways text, etc.). If your work flow requires passing documents back-and-forth between R and Excel while retaining the original Excel formatting, try packages such as openxlsx.", + "text": "Benefits\nYou will find that using R offers immense benefits in time saved, more consistent and accurate analysis, reproducibility, shareability, and faster error-correction. Like any new software there is a learning “curve” of time you must invest to become familiar. The dividends will be significant and immense scope of new possibilities will open to you with R.\nExcel is a well-known software that can be easy for a beginner to use to produce simple analysis and visualizations with “point-and-click”. In comparison, it can take a couple weeks to become comfortable with R functions and interface. However, R has evolved in recent years to become much more friendly to beginners.\nMany Excel workflows rely on memory and on repetition - this means there is much opportunity for error. Furthermore, generally the data cleaning, analysis methodology, and equations used are hidden from view. It can require substantial time for a new colleague to learn what an Excel workbook is doing and how to troubleshoot it. With R, all the steps are explicitly written in the script and can be easily viewed, edited, corrected, and applied to other datasets.\nTo begin your transition from Excel to R you must adjust your mindset in a few important ways:\n\n\nTidy data\nUse machine-readable “tidy” data instead of messy “human-readable” data. These are the three main requirements for “tidy” data, as explained in this tutorial on “tidy” data in R:\n\nEach variable must have its own column.\n\nEach observation must have its own row.\n\nEach value must have its own cell.\n\nTo Excel users - think of the role that Excel “tables” play in standardizing data and making the format more predictable.\nAn example of “tidy” data would be the case linelist used throughout this handbook - each variable is contained within one column, each observation (one case) has it’s own row, and every value is in just one cell. Below you can view the first 50 rows of the linelist:\n\n\n\n\n\n\nThe main reason you might encounter non-tidy data is because many Excel spreadsheets are designed to prioritize easy reading by humans, not easy reading by machines/software.\nTo help you see the difference, below are some fictional examples of non-tidy data that prioritize human-readability over machine-readability:\n\n\n\n\n\n\n\n\n\nProblems: In the spreadsheet above, there are merged cells which are not easily digested by R. Which row should be considered the “header” is not clear. A color-based dictionary is to the right side and cell values are represented by colors - which is also not easily interpreted by R (nor by humans with color-blindness!). Furthermore, different pieces of information are combined into one cell (multiple partner organizations working in one area, or the status “TBC” in the same cell as “Partner D”).\n\n\n\n\n\n\n\n\n\nProblems: In the spreadsheet above, there are numerous extra empty rows and columns within the dataset - this will cause cleaning headaches in R. Furthermore, the GPS coordinates are spread across two rows for a given treatment center. As a side note - the GPS coordinates are in two different formats!\n“Tidy” datasets may not be as readable to a human eye, but they make data cleaning and analysis much easier! Tidy data can be stored in various formats, for example “long” or “wide”“(see page on Pivoting data), but the principles above are still observed.\n\n\nFunctions\nThe R word “function” might be new, but the concept exists in Excel too as formulas. Formulas in Excel also require precise syntax (e.g. placement of semicolons and parentheses). All you need to do is learn a few new functions and how they work together in R.\n\n\nScripts\nInstead of clicking buttons and dragging cells you will be writing every step and procedure into a “script”. Excel users may be familiar with “VBA macros” which also employ a scripting approach.\nThe R script consists of step-by-step instructions. This allows any colleague to read the script and easily see the steps you took. This also helps de-bug errors or inaccurate calculations. See the R basics section on scripts for examples.\nHere is an example of an R script:\n\n\n\n\n\n\n\n\n\n\n\nExcel-to-R resources\nHere are some links to tutorials to help you transition to R from Excel:\n\nR vs. Excel\n\nRStudio course in R for Excel users\n\n\n\nR-Excel interaction\nR has robust ways to import Excel workbooks, work with the data, export/save Excel files, and work with the nuances of Excel sheets.\nIt is true that some of the more aesthetic Excel formatting can get lost in translation (e.g. italics, sideways text, etc.). If your work flow requires passing documents back-and-forth between R and Excel while retaining the original Excel formatting, try packages such as openxlsx.", "crumbs": [ "Basics", "4  Transition to R" @@ -307,7 +307,7 @@ "href": "new_pages/transition_to_R.html#from-stata", "title": "4  Transition to R", "section": "4.2 From Stata", - "text": "4.2 From Stata\n\nComing to R from Stata\nMany epidemiologists are first taught how to use Stata, and it can seem daunting to move into R. However, if you are a comfortable Stata user then the jump into R is certainly more manageable than you might think. While there are some key differences between Stata and R in how data can be created and modified, as well as how analysis functions are implemented – after learning these key differences you will be able to translate your skills.\nBelow are some key translations between Stata and R, which may be handy as your review this guide.\nGeneral notes\n\n\n\nSTATA\nR\n\n\n\n\nYou can only view and manipulate one dataset at a time\nYou can view and manipulate multiple datasets at the same time, therefore you will frequently have to specify your dataset within the code\n\n\nOnline community available through https://www.statalist.org/\nOnline community available through RStudio, StackOverFlow, and R-bloggers\n\n\nPoint and click functionality as an option\nMinimal point and click functionality\n\n\nHelp for commands available by help [command]\nHelp available by [function]? or search in the Help pane\n\n\nComment code using * or /// or /* TEXT */\nComment code using #\n\n\nAlmost all commands are built-in to Stata. New/user-written functions can be installed as ado files using ssc install [package]\nR installs with base functions, but typical use involves installing other packages from CRAN (see page on R basics)\n\n\nAnalysis is usually written in a do file\nAnalysis written in an R script in the RStudio source pane. R markdown scripts are an alternative.\n\n\n\nWorking directory\n\n\n\nSTATA\nR\n\n\n\n\nWorking directories involve absolute filepaths (e.g. “C:/usename/documents/projects/data/”)\nWorking directories can be either absolute, or relative to a project root folder by using the here package (see Import and export)\n\n\nSee current working directory with pwd\nUse getwd() or here() (if using the here package), with empty parentheses\n\n\nSet working directory with cd “folder location”\nUse setwd(“folder location”), or set_here(\"folder location) (if using here package)\n\n\n\nImporting and viewing data\n\n\n\nSTATA\nR\n\n\n\n\nSpecific commands per file type\nUse import() from rio package for almost all filetypes. Specific functions exist as alternatives (see Import and export)\n\n\nReading in csv files is done by import delimited “filename.csv”\nUse import(\"filename.csv\")\n\n\nReading in xslx files is done by import excel “filename.xlsx”\nUse import(\"filename.xlsx\")\n\n\nBrowse your data in a new window using the command browse\nView a dataset in the RStudio source pane using View(dataset). You need to specify your dataset name to the function in R because multiple datasets can be held at the same time. Note capital “V” in this function\n\n\nGet a high-level overview of your dataset using summarize, which provides the variable names and basic information\nGet a high-level overview of your dataset using summary(dataset)\n\n\n\nBasic data manipulation\n\n\n\nSTATA\nR\n\n\n\n\nDataset columns are often referred to as “variables”\nMore often referred to as “columns” or sometimes as “vectors” or “variables”\n\n\nNo need to specify the dataset\nIn each of the below commands, you need to specify the dataset - see the page on Cleaning data and core functions for examples\n\n\nNew variables are created using the command generate varname =\nGenerate new variables using the function mutate(varname = ). See page on Cleaning data and core functions for details on all the below dplyr functions.\n\n\nVariables are renamed using rename old_name new_name\nColumns can be renamed using the function rename(new_name = old_name)\n\n\nVariables are dropped using drop varname\nColumns can be removed using the function select() with the column name in the parentheses following a minus sign\n\n\nFactor variables can be labeled using a series of commands such as label define\nLabeling values can done by converting the column to Factor class and specifying levels. See page on Factors. Column names are not typically labeled as they are in Stata.\n\n\n\nDescriptive analysis\n\n\n\nSTATA\nR\n\n\n\n\nTabulate counts of a variable using tab varname\nProvide the dataset and column name to table() such as table(dataset$colname). Alternatively, use count(varname) from the dplyr package, as explained in Grouping data\n\n\nCross-tabulaton of two variables in a 2x2 table is done with tab varname1 varname2\nUse table(dataset$varname1, dataset$varname2 or count(varname1, varname2)\n\n\n\nWhile this list gives an overview of the basics in translating Stata commands into R, it is not exhaustive. There are many other great resources for Stata users transitioning to R that could be of interest:\n\nhttps://dss.princeton.edu/training/RStata.pdf\n\nhttps://clanfear.github.io/Stata_R_Equivalency/docs/r_stata_commands.html\n\nhttp://r4stats.com/books/r4stata/", + "text": "4.2 From Stata\n\nComing to R from Stata\nMany epidemiologists are first taught how to use Stata, and it can seem daunting to move into R. However, if you are a comfortable Stata user then the jump into R is certainly more manageable than you might think. While there are some key differences between Stata and R in how data can be created and modified, as well as how analysis functions are implemented – after learning these key differences you will be able to translate your skills.\nBelow are some key translations between Stata and R, which may be handy as your review this guide.\nPlease also see this cheatsheet for Stata to R.\nGeneral notes\n\n\n\nStata\nR\n\n\n\n\nYou can only view and manipulate one dataset at a time\nYou can view and manipulate multiple datasets at the same time, therefore you will frequently have to specify your dataset within the code\n\n\nOnline community available through https://www.statalist.org/\nOnline community available through RStudio, StackOverFlow, and R-bloggers\n\n\nPoint and click functionality as an option\nMinimal point and click functionality\n\n\nHelp for commands available by help [command]\nHelp available by [function]? or search in the Help pane\n\n\nComment code using * or /// or /* TEXT */\nComment code using #\n\n\nAlmost all commands are built-in to Stata. New/user-written functions can be installed as ado files using ssc install [package]\nR installs with base functions, but typical use involves installing other packages from CRAN (see page on R basics)\n\n\nAnalysis is usually written in a do file\nAnalysis written in an R script in the RStudio source pane. R markdown scripts are an alternative.\n\n\n\nWorking directory\n\n\n\nStata\nR\n\n\n\n\nWorking directories involve absolute filepaths (e.g. “C:/usename/documents/projects/data/”)\nWorking directories can be either absolute, or relative to a project root folder by using the here package (see Import and export)\n\n\nSee current working directory with pwd\nUse getwd() or here() (if using the here package), with empty parentheses\n\n\nSet working directory with cd “folder location”\nUse setwd(“folder location”), or set_here(\"folder location) (if using here package)\n\n\n\nImporting and viewing data\n\n\n\nStata\nR\n\n\n\n\nSpecific commands per file type\nUse import() from rio package for almost all filetypes. Specific functions exist as alternatives (see Import and export)\n\n\nReading in csv files is done by import delimited “filename.csv”\nUse import(\"filename.csv\")\n\n\nReading in xslx files is done by import excel “filename.xlsx”\nUse import(\"filename.xlsx\")\n\n\nBrowse your data in a new window using the command browse\nView a dataset in the RStudio source pane using View(dataset). You need to specify your dataset name to the function in R because multiple datasets can be held at the same time. Note capital “V” in this function\n\n\nGet a high-level overview of your dataset using summarize, which provides the variable names and basic information\nGet a high-level overview of your dataset using summary(dataset)\n\n\n\nBasic data manipulation\n\n\n\nStata\nR\n\n\n\n\nDataset columns are often referred to as “variables”\nMore often referred to as “columns” or sometimes as “vectors” or “variables”\n\n\nNo need to specify the dataset\nIn each of the below commands, you need to specify the dataset - see the page on Cleaning data and core functions for examples\n\n\nNew variables are created using the command generate varname =\nGenerate new variables using the function mutate(varname = ). See page on Cleaning data and core functions for details on all the below dplyr functions.\n\n\nVariables are renamed using rename old_name new_name\nColumns can be renamed using the function rename(new_name = old_name)\n\n\nVariables are dropped using drop varname\nColumns can be removed using the function select() with the column name in the parentheses following a minus sign\n\n\nFactor variables can be labeled using a series of commands such as label define\nLabeling values can done by converting the column to Factor class and specifying levels. See page on Factors. Column names are not typically labeled as they are in Stata.\n\n\n\nDescriptive analysis\n\n\n\nStata\nR\n\n\n\n\nTabulate counts of a variable using tab varname\nProvide the dataset and column name to table() such as table(dataset$colname). Alternatively, use count(varname) from the dplyr package, as explained in Grouping data\n\n\nCross-tabulaton of two variables in a 2x2 table is done with tab varname1 varname2\nUse table(dataset$varname1, dataset$varname2 or count(varname1, varname2)\n\n\n\nWhile this list gives an overview of the basics in translating Stata commands into R, it is not exhaustive. There are many other great resources for Stata users transitioning to R that could be of interest:\n\nR and Stata Equivalencies\nR for Stata users", "crumbs": [ "Basics", "4  Transition to R" @@ -318,7 +318,7 @@ "href": "new_pages/transition_to_R.html#from-sas", "title": "4  Transition to R", "section": "4.3 From SAS", - "text": "4.3 From SAS\n\nComing from SAS to R\nSAS is commonly used at public health agencies and academic research fields. Although transitioning to a new language is rarely a simple process, understanding key differences between SAS and R may help you start to navigate the new language using your native language. Below outlines the key translations in data management and descriptive analysis between SAS and R.\nGeneral notes\n\n\n\nSAS\nR\n\n\n\n\nOnline community available through SAS Customer Support\nOnline community available through RStudio, StackOverFlow, and R-bloggers\n\n\nHelp for commands available by help [command]\nHelp available by [function]? or search in the Help pane\n\n\nComment code using * TEXT ; or /* TEXT */\nComment code using #\n\n\nAlmost all commands are built-in. Users can write new functions using SAS macro, SAS/IML, SAS Component Language (SCL), and most recently, procedures Proc Fcmp and Proc Proto\nR installs with base functions, but typical use involves installing other packages from CRAN (see page on R basics)\n\n\nAnalysis is usually conducted by writing a SAS program in the Editor window.\nAnalysis written in an R script in the RStudio source pane. R markdown scripts are an alternative.\n\n\n\nWorking directory\n\n\n\nSAS\nR\n\n\n\n\nWorking directories can be either absolute, or relative to a project root folder by defining the root folder using %let rootdir=/root path; %include “&rootdir/subfoldername/filename”\nWorking directories can be either absolute, or relative to a project root folder by using the here package (see Import and export))\n\n\nSee current working directory with %put %sysfunc(getoption(work));\nUse getwd() or here() (if using the here package), with empty parentheses\n\n\nSet working directory with libname “folder location”\nUse setwd(“folder location”), or set_here(\"folder location) if using here package\n\n\n\nImporting and viewing data\n\n\n\nSAS\nR\n\n\n\n\nUse Proc Import procedure or using Data Step Infile statement.\nUse import() from rio package for almost all filetypes. Specific functions exist as alternatives (see Import and export)\n\n\nReading in csv files is done by using Proc Import datafile=”filename.csv” out=work.filename dbms=CSV; run; OR using Data Step Infile statement\nUse import(\"filename.csv\")\n\n\nReading in xslx files is done by using Proc Import datafile=”filename.xlsx” out=work.filename dbms=xlsx; run; OR using Data Step Infile statement\nUse import(“filename.xlsx”)\n\n\nBrowse your data in a new window by opening the Explorer window and select desired library and the dataset\nView a dataset in the RStudio source pane using View(dataset). You need to specify your dataset name to the function in R because multiple datasets can be held at the same time. Note capital “V” in this function\n\n\n\nBasic data manipulation\n\n\n\nSAS\nR\n\n\n\n\nDataset columns are often referred to as “variables”\nMore often referred to as “columns” or sometimes as “vectors” or “variables”\n\n\nNo special procedures are needed to create a variable. New variables are created simply by typing the new variable name, followed by an equal sign, and then an expression for the value\nGenerate new variables using the function mutate(). See page on Cleaning data and core functions for details on all the below dplyr functions.\n\n\nVariables are renamed using rename *old_name=new_name*\nColumns can be renamed using the function rename(new_name = old_name)\n\n\nVariables are kept using **keep**=varname\nColumns can be selected using the function select() with the column name in the parentheses\n\n\nVariables are dropped using **drop**=varname\nColumns can be removed using the function select() with the column name in the parentheses following a minus sign\n\n\nFactor variables can be labeled in the Data Step using Label statement\nLabeling values can done by converting the column to Factor class and specifying levels. See page on Factors. Column names are not typically labeled.\n\n\nRecords are selected using Where or If statement in the Data Step. Multiple selection conditions are separated using “and” command.\nRecords are selected using the function filter() with multiple selection conditions separated either by an AND operator (&) or a comma\n\n\nDatasets are combined using Merge statement in the Data Step. The datasets to be merged need to be sorted first using Proc Sort procedure.\ndplyr package offers a few functions for merging datasets. See page Joining Data for details.\n\n\n\nDescriptive analysis\n\n\n\nSAS\nR\n\n\n\n\nGet a high-level overview of your dataset using Proc Summary procedure, which provides the variable names and descriptive statistics\nGet a high-level overview of your dataset using summary(dataset) or skim(dataset) from the skimr package\n\n\nTabulate counts of a variable using proc freq data=Dataset; Tables varname; Run;\nSee the page on Descriptive tables. Options include table() from base R, and tabyl() from janitor package, among others. Note you will need to specify the dataset and column name as R holds multiple datasets.\n\n\nCross-tabulation of two variables in a 2x2 table is done with proc freq data=Dataset; Tables rowvar*colvar; Run;\nAgain, you can use table(), tabyl() or other options as described in the Descriptive tables page.\n\n\n\nSome useful resources:\nR for SAS and SPSS Users (2011)\nSAS and R, Second Edition (2014)", + "text": "4.3 From SAS\n\nComing from SAS to R\nSAS is commonly used at public health agencies and academic research fields. Although transitioning to a new language is rarely a simple process, understanding key differences between SAS and R may help you start to navigate the new language using your native language. Below outlines the key translations in data management and descriptive analysis between SAS and R.\nGeneral notes\n\n\n\nSAS\nR\n\n\n\n\nOnline community available through SAS Customer Support\nOnline community available through RStudio, StackOverFlow, and R-bloggers\n\n\nHelp for commands available by help [command]\nHelp available by [function]? or search in the Help pane\n\n\nComment code using * TEXT ; or /* TEXT */\nComment code using #\n\n\nAlmost all commands are built-in. Users can write new functions using SAS macro, SAS/IML, SAS Component Language (SCL), and most recently, procedures Proc Fcmp and Proc Proto\nR installs with base functions, but typical use involves installing other packages from CRAN (see page on R basics)\n\n\nAnalysis is usually conducted by writing a SAS program in the Editor window.\nAnalysis written in an R script in the RStudio source pane. R markdown scripts are an alternative.\n\n\n\nWorking directory\n\n\n\nSAS\nR\n\n\n\n\nWorking directories can be either absolute, or relative to a project root folder by defining the root folder using %let rootdir=/root path; %include “&rootdir/subfoldername/filename”\nWorking directories can be either absolute, or relative to a project root folder by using the here package (see Import and export)\n\n\nSee current working directory with %put %sysfunc(getoption(work));\nUse getwd() or here() (if using the here package), with empty parentheses\n\n\nSet working directory with libname “folder location”\nUse setwd(“folder location”), or set_here(\"folder location) if using here package\n\n\n\nImporting and viewing data\n\n\n\nSAS\nR\n\n\n\n\nUse Proc Import procedure or using Data Step Infile statement.\nUse import() from rio package for almost all filetypes. Specific functions exist as alternatives (see Import and export)\n\n\nReading in csv files is done by using Proc Import datafile=”filename.csv” out=work.filename dbms=CSV; run; OR using Data Step Infile statement\nUse import(\"filename.csv\")\n\n\nReading in xslx files is done by using Proc Import datafile=”filename.xlsx” out=work.filename dbms=xlsx; run; OR using Data Step Infile statement\nUse import(“filename.xlsx”)\n\n\nBrowse your data in a new window by opening the Explorer window and select desired library and the dataset\nView a dataset in the RStudio source pane using View(dataset). You need to specify your dataset name to the function in R because multiple datasets can be held at the same time. Note capital “V” in this function\n\n\n\nBasic data manipulation\n\n\n\nSAS\nR\n\n\n\n\nDataset columns are often referred to as “variables”\nMore often referred to as “columns” or sometimes as “vectors” or “variables”\n\n\nNo special procedures are needed to create a variable. New variables are created simply by typing the new variable name, followed by an equal sign, and then an expression for the value\nGenerate new variables using the function mutate(). See page on Cleaning data and core functions for details on all the below dplyr functions.\n\n\nVariables are renamed using rename *old_name=new_name*\nColumns can be renamed using the function rename(new_name = old_name)\n\n\nVariables are kept using **keep**=varname\nColumns can be selected using the function select() with the column name in the parentheses\n\n\nVariables are dropped using **drop**=varname\nColumns can be removed using the function select() with the column name in the parentheses following a minus sign\n\n\nFactor variables can be labeled in the Data Step using Label statement\nLabeling values can done by converting the column to Factor class and specifying levels. See page on Factors. Column names are not typically labeled.\n\n\nRecords are selected using Where or If statement in the Data Step. Multiple selection conditions are separated using “and” command.\nRecords are selected using the function filter() with multiple selection conditions separated either by an AND operator (&) or a comma\n\n\nDatasets are combined using Merge statement in the Data Step. The datasets to be merged need to be sorted first using Proc Sort procedure.\ndplyr package offers a few functions for merging datasets. See page Joining Data for details.\n\n\n\nDescriptive analysis\n\n\n\nSAS\nR\n\n\n\n\nGet a high-level overview of your dataset using Proc Summary procedure, which provides the variable names and descriptive statistics\nGet a high-level overview of your dataset using summary(dataset) or skim(dataset) from the skimr package\n\n\nTabulate counts of a variable using proc freq data=Dataset; Tables varname; Run;\nSee the page on Descriptive tables. Options include table() from base R, and tabyl() from janitor package, among others. Note you will need to specify the dataset and column name as R holds multiple datasets.\n\n\nCross-tabulation of two variables in a 2x2 table is done with proc freq data=Dataset; Tables rowvar*colvar; Run;\nAgain, you can use table(), tabyl() or other options as described in the Descriptive tables page.\n\n\n\nSome useful resources:\nSAS for R Users: A Book for Data Scientists (2019)\nAnalyzing Health Data in R for SAS Users (2018)\nR for SAS and SPSS Users (2011)\nSAS and R, Second Edition (2014)", "crumbs": [ "Basics", "4  Transition to R" @@ -329,7 +329,7 @@ "href": "new_pages/transition_to_R.html#data-interoperability", "title": "4  Transition to R", "section": "4.4 Data interoperability", - "text": "4.4 Data interoperability\n\nsee the Import and export(importing.qmd) page for details on how the R package rio can import and export files such as STATA .dta files, SAS .xpt and.sas7bdat files, SPSS .por and.sav files, and many others.", + "text": "4.4 Data interoperability\n\nSee the Import and export page for details on how the R package rio can import and export files such as Stata .dta files, SAS .xpt and.sas7bdat files, SPSS .por and.sav files, and many others.", "crumbs": [ "Basics", "4  Transition to R" @@ -340,7 +340,7 @@ "href": "new_pages/packages_suggested.html", "title": "5  Suggested packages", "section": "", - "text": "5.1 Packages from CRAN\n##########################################\n# List of useful epidemiology R packages #\n##########################################\n\n# This script uses the p_load() function from pacman R package, \n# which installs if package is absent, and loads for use if already installed\n\n\n# Ensures the package \"pacman\" is installed\nif (!require(\"pacman\")) install.packages(\"pacman\")\n\n\n# Packages available from CRAN\n##############################\npacman::p_load(\n \n # learning R\n ############\n learnr, # interactive tutorials in RStudio Tutorial pane\n swirl, # interactive tutorials in R console\n \n # project and file management\n #############################\n here, # file paths relative to R project root folder\n rio, # import/export of many types of data\n openxlsx, # import/export of multi-sheet Excel workbooks \n \n # package install and management\n ################################\n pacman, # package install/load\n renv, # managing versions of packages when working in collaborative groups\n remotes, # install from github\n \n # General data management\n #########################\n tidyverse, # includes many packages for tidy data wrangling and presentation\n #dplyr, # data management\n #tidyr, # data management\n #ggplot2, # data visualization\n #stringr, # work with strings and characters\n #forcats, # work with factors \n #lubridate, # work with dates\n #purrr # iteration and working with lists\n linelist, # cleaning linelists\n naniar, # assessing missing data\n \n # statistics \n ############\n janitor, # tables and data cleaning\n gtsummary, # making descriptive and statistical tables\n rstatix, # quickly run statistical tests and summaries\n broom, # tidy up results from regressions\n lmtest, # likelihood-ratio tests\n easystats,\n # parameters, # alternative to tidy up results from regressions\n # see, # alternative to visualise forest plots \n \n # epidemic modeling\n ###################\n epicontacts, # Analysing transmission networks\n EpiNow2, # Rt estimation\n EpiEstim, # Rt estimation\n projections, # Incidence projections\n incidence2, # Make epicurves and handle incidence data\n i2extras, # Extra functions for the incidence2 package\n epitrix, # Useful epi functions\n distcrete, # Discrete delay distributions\n \n \n # plots - general\n #################\n #ggplot2, # included in tidyverse\n cowplot, # combining plots \n # patchwork, # combining plots (alternative) \n RColorBrewer, # color scales\n ggnewscale, # to add additional layers of color schemes\n\n \n # plots - specific types\n ########################\n DiagrammeR, # diagrams using DOT language\n incidence2, # epidemic curves\n gghighlight, # highlight a subset\n ggrepel, # smart labels\n plotly, # interactive graphics\n gganimate, # animated graphics \n\n \n # gis\n ######\n sf, # to manage spatial data using a Simple Feature format\n tmap, # to produce simple maps, works for both interactive and static maps\n OpenStreetMap, # to add OSM basemap in ggplot map\n spdep, # spatial statistics \n \n # routine reports\n #################\n rmarkdown, # produce PDFs, Word Documents, Powerpoints, and HTML files\n reportfactory, # auto-organization of R Markdown outputs\n officer, # powerpoints\n \n # dashboards\n ############\n flexdashboard, # convert an R Markdown script into a dashboard\n shiny, # interactive web apps\n \n # tables for presentation\n #########################\n knitr, # R Markdown report generation and html tables\n flextable, # HTML tables\n #DT, # HTML tables (alternative)\n #gt, # HTML tables (alternative)\n #huxtable, # HTML tables (alternative) \n \n # phylogenetics\n ###############\n ggtree, # visualization and annotation of trees\n ape, # analysis of phylogenetics and evolution\n treeio # to visualize phylogenetic files\n \n)", + "text": "5.1 Packages from CRAN\n##########################################\n# List of useful epidemiology R packages #\n##########################################\n\n# This script uses the p_load() function from pacman R package, \n# which installs if package is absent, and loads for use if already installed\n\n\n# Ensures the package \"pacman\" is installed\nif (!require(\"pacman\")) install.packages(\"pacman\")\n\n\n# Packages available from CRAN\n##############################\npacman::p_load(\n \n # learning R\n ############\n learnr, # interactive tutorials in RStudio Tutorial pane\n swirl, # interactive tutorials in R console\n \n # project and file management\n #############################\n here, # file paths relative to R project root folder\n rio, # import/export of many types of data\n openxlsx, # import/export of multi-sheet Excel workbooks \n \n # package install and management\n ################################\n pacman, # package install/load\n renv, # managing versions of packages when working in collaborative groups\n remotes, # install from github\n \n # General data management\n #########################\n tidyverse, # includes many packages for tidy data wrangling and presentation\n #dplyr, # data management\n #tidyr, # data management\n #ggplot2, # data visualization\n #stringr, # work with strings and characters\n #forcats, # work with factors \n #lubridate, # work with dates\n #purrr # iteration and working with lists\n linelist, # cleaning linelists\n naniar, # assessing missing data\n \n # statistics \n ############\n janitor, # tables and data cleaning\n gtsummary, # making descriptive and statistical tables\n rstatix, # quickly run statistical tests and summaries\n broom, # tidy up results from regressions\n lmtest, # likelihood-ratio tests\n easystats,\n # parameters, # alternative to tidy up results from regressions\n # see, # alternative to visualise forest plots \n \n # epidemic modeling\n ###################\n epicontacts, # Analysing transmission networks\n EpiNow2, # Rt estimation\n EpiEstim, # Rt estimation\n projections, # Incidence projections\n incidence2, # Make epicurves and handle incidence data\n i2extras, # Extra functions for the incidence2 package\n epitrix, # Useful epi functions\n distcrete, # Discrete delay distributions\n \n \n # plots - general\n #################\n #ggplot2, # included in tidyverse\n patchwork, # combining plots\n RColorBrewer, # color scales\n ggnewscale, # to add additional layers of color schemes\n\n \n # plots - specific types\n ########################\n DiagrammeR, # diagrams using DOT language\n incidence2, # epidemic curves\n gghighlight, # highlight a subset\n ggrepel, # smart labels\n plotly, # interactive graphics\n gganimate, # animated graphics \n ggalluvial, # for alluvial/sankey diagrams\n\n \n # gis\n ######\n sf, # to manage spatial data using a Simple Feature format\n tmap, # to produce simple maps, works for both interactive and static maps\n OpenStreetMap, # to add OSM basemap in ggplot map\n tidyterra, # to plot basemaps\n maptiles, # for creating basemaps\n spdep, # spatial statistics \n \n # routine reports\n #################\n rmarkdown, # produce PDFs, Word Documents, Powerpoints, and HTML files\n reportfactory, # auto-organization of R Markdown outputs\n officer, # powerpoints\n \n # dashboards\n ############\n flexdashboard, # convert an R Markdown script into a dashboard\n shiny, # interactive web apps\n \n # tables for presentation\n #########################\n knitr, # R Markdown report generation and html tables\n flextable, # HTML tables\n #DT, # HTML tables (alternative)\n #gt, # HTML tables (alternative)\n #huxtable, # HTML tables (alternative) \n \n # phylogenetics\n ###############\n ggtree, # visualization and annotation of trees\n ape, # analysis of phylogenetics and evolution\n treeio # to visualize phylogenetic files\n \n)", "crumbs": [ "Basics", "5  Suggested packages" @@ -351,7 +351,7 @@ "href": "new_pages/packages_suggested.html#packages-from-github", "title": "5  Suggested packages", "section": "5.2 Packages from Github", - "text": "5.2 Packages from Github\nBelow are commmands to install two packages directly from Github repositories.\n\nThe development version of epicontacts contains the ability to make transmission trees with an temporal x-axis\n\nThe epirhandbook package contains all the example data for this handbook and can be used to download the offline version of the handbook.\n\n\n# Packages to download from Github (not available on CRAN)\n##########################################################\n\n# Development version of epicontacts (for transmission chains with a time x-axis)\npacman::p_install_gh(\"reconhub/epicontacts@timeline\")\n\n# The package for this handbook, which includes all the example data \npacman::p_install_gh(\"appliedepi/epirhandbook\")", + "text": "5.2 Packages from Github\nBelow are commmands to install two packages directly from Github repositories.\n\nThe epirhandbook package contains all the example data for this handbook and can be used to download the offline version of the handbook.\n\n\n# Packages to download from Github (not available on CRAN)\n##########################################################\n\n# The package for this handbook, which includes all the example data \npacman::p_install_gh(\"appliedepi/epirhandbook\")", "crumbs": [ "Basics", "5  Suggested packages" @@ -362,7 +362,7 @@ "href": "new_pages/r_projects.html", "title": "6  R projects", "section": "", - "text": "6.1 Suggested use\nA common, efficient, and trouble-free way to use R is to combine these 3 elements. One discrete work project is hosted within one R project. Each element is described in the sections below.", + "text": "6.1 Suggested use\nA common, efficient, and trouble-free way to use R is to combine these 3 elements. Each element is described in the sections below.", "crumbs": [ "Basics", "6  R projects" @@ -373,7 +373,7 @@ "href": "new_pages/r_projects.html#suggested-use", "title": "6  R projects", "section": "", - "text": "An R project\n\nA self-contained working environment with folders for data, scripts, outputs, etc.\n\n\nThe here package for relative filepaths\n\nFilepaths are written relative to the root folder of the R project - see Import and export for more information\n\n\nThe rio package for importing/exporting\n\nimport() and export() handle any file type by by its extension (e.g. .csv, .xlsx, .png)", + "text": "An R project - A self-contained working environment that enables the use of file paths written relative to its root folder. See Import and export for more information.\n\nThe rio package - Its functions import() and export() that handle any file type by its extension (e.g. .csv, .xlsx, .png).\n\nThe here package - Its function here() allows easy creation of file paths, including within automated reports.", "crumbs": [ "Basics", "6  R projects" @@ -417,7 +417,7 @@ "href": "new_pages/importing.html", "title": "7  Import and export", "section": "", - "text": "7.1 Overview\nWhen you import a “dataset” into R, you are generally creating a new data frame object in your R environment and defining it as an imported file (e.g. Excel, CSV, TSV, RDS) that is located in your folder directories at a certain file path/address.\nYou can import/export many types of files, including those created by other statistical programs (SAS, STATA, SPSS). You can also connect to relational databases.\nR even has its own data formats:", + "text": "7.1 Overview\nWhen you import a “dataset” into R, you are generally creating a new data frame object in your R environment and defining it as an imported file (e.g. Excel, CSV, TSV, RDS) which is located in your folder directories at a certain file path/address.\nYou can import/export many types of files, including those created by other statistical programs (SAS, STATA, SPSS). You can also connect to relational databases.\nR even has its own data formats:", "crumbs": [ "Basics", "7  Import and export" @@ -428,7 +428,7 @@ "href": "new_pages/importing.html#overview", "title": "7  Import and export", "section": "", - "text": "An RDS file (.rds) stores a single R object such as a data frame. These are useful to store cleaned data, as they maintain R column classes. Read more in this section.\n\nAn RData file (.Rdata) can be used to store multiple objects, or even a complete R workspace. Read more in this section.", + "text": "An RDS file (.rds) stores a single R object such as a data frame. These are useful to store cleaned data, as they maintain R column classes. Read more in this section\n\nAn RData file (.Rdata) can be used to store multiple objects, or even a complete R workspace. Read more in this section", "crumbs": [ "Basics", "7  Import and export" @@ -439,7 +439,7 @@ "href": "new_pages/importing.html#the-rio-package", "title": "7  Import and export", "section": "7.2 The rio package", - "text": "7.2 The rio package\nThe R package we recommend is: rio. The name “rio” is an abbreviation of “R I/O” (input/output).\nIts functions import() and export() can handle many different file types (e.g. .xlsx, .csv, .rds, .tsv). When you provide a file path to either of these functions (including the file extension like “.csv”), rio will read the extension and use the correct tool to import or export the file.\nThe alternative to using rio is to use functions from many other packages, each of which is specific to a type of file. For example, read.csv() (base R), read.xlsx() (openxlsx package), and write_csv() (readr pacakge), etc. These alternatives can be difficult to remember, whereas using import() and export() from rio is easy.\nrio’s functions import() and export() use the appropriate package and function for a given file, based on its file extension. See the end of this page for a complete table of which packages/functions rio uses in the background. It can also be used to import STATA, SAS, and SPSS files, among dozens of other file types.\nImport/export of shapefiles requires other packages, as detailed in the page on GIS basics.", + "text": "7.2 The rio package\nThe R package we recommend is: rio. The name “rio” is an abbreviation of “R I/O” (input/output).\nIts functions import() and export() can handle many different file types (e.g. .xlsx, .csv, .rds, .tsv). When you provide a file path to either of these functions (including the file extension like “.csv”), rio will read the extension and use the correct tool to import or export the file.\nIf used within an R project, an importing command can be as simple as:\n\nlinelist <- import(\"linelist_raw.xlsx\")\n\nThe alternative to using rio is to use functions from many other packages, each of which is specific to a type of file. For example, read.csv() (base R), read.xlsx() (openxlsx package), and write_csv() (readr pacakge), etc. These alternatives can be difficult to remember, whereas using import() and export() from rio is easy.\nrio’s functions import() and export() use the appropriate package and function for a given file, based on its file extension. See the end of this page for a complete table of which packages/functions rio uses in the background. It can also be used to import STATA, SAS, and SPSS files, among dozens of other file types.\nImport/export of shapefiles requires other packages, as detailed in the page on GIS basics.", "crumbs": [ "Basics", "7  Import and export" @@ -450,7 +450,7 @@ "href": "new_pages/importing.html#here", "title": "7  Import and export", "section": "7.3 The here package", - "text": "7.3 The here package\nThe package here and its function here() make it easy to tell R where to find and to save your files - in essence, it builds file paths.\nUsed in conjunction with an R project, here allows you to describe the location of files in your R project in relation to the R project’s root directory (the top-level folder). This is useful when the R project may be shared or accessed by multiple people/computers. It prevents complications due to the unique file paths on different computers (e.g. \"C:/Users/Laura/Documents...\" by “starting” the file path in a place common to all users (the R project root).\nThis is how here() works within an R project:\n\nWhen the here package is first loaded within the R project, it places a small file called “.here” in the root folder of your R project as a “benchmark” or “anchor”\n\nIn your scripts, to reference a file in the R project’s sub-folders, you use the function here() to build the file path in relation to that anchor\nTo build the file path, write the names of folders beyond the root, within quotes, separated by commas, finally ending with the file name and file extension as shown below\n\nhere() file paths can be used for both importing and exporting\n\nFor example, below, the function import() is being provided a file path constructed with here().\n\nlinelist <- import(here(\"data\", \"linelists\", \"ebola_linelist.xlsx\"))\n\nThe command here(\"data\", \"linelists\", \"ebola_linelist.xlsx\") is actually providing the full file path that is unique to the user’s computer:\n\"C:/Users/Laura/Documents/my_R_project/data/linelists/ebola_linelist.xlsx\"\nThe beauty is that the R command using here() can be successfully run on any computer accessing the R project.\nTIP: If you are unsure where the “.here” root is set to, run the function here() with empty parentheses.\nRead more about the here package at this link.", + "text": "7.3 The here package\nThe package here and its function here() make it easy to tell R where to find and to save your files - in essence, it builds file paths.\nUsed in conjunction with an R project, here allows you to describe the location of files in your R project in relation to the R project’s root directory (the top-level folder). This is useful when the R project may be shared or accessed by multiple people/computers. It prevents complications due to the unique file paths on different computers (e.g. \"C:/Users/Laura/Documents...\" by “starting” the file path in a place common to all users (the R project root).\nThis is how here() works within an R project:\n\nWhen the here package is first loaded within the R project, it places a small file called “.here” in the root folder of your R project as a “benchmark” or “anchor”.\n\nIn your scripts, to reference a file in the R project’s sub-folders, you use the function here() to build the file path in relation to that anchor.\nTo build the file path, write the names of folders beyond the root, within quotes, separated by commas, finally ending with the file name and file extension as shown below.\n\nhere() file paths can be used for both importing and exporting.\n\nFor example, below, the function import() is being provided a file path constructed with here().\n\nlinelist <- import(here(\"data\", \"linelists\", \"ebola_linelist.xlsx\"))\n\nThe command here(\"data\", \"linelists\", \"ebola_linelist.xlsx\") is actually providing the full file path that is unique to the user’s computer:\n\"C:/Users/Laura/Documents/my_R_project/data/linelists/ebola_linelist.xlsx\"\nThe beauty is that the R command using here() can be successfully run on any computer accessing the R project.\nTIP: If you are unsure where the “.here” root is set to, run the function here() with empty parentheses.\nRead more about the here package at this link.", "crumbs": [ "Basics", "7  Import and export" @@ -460,8 +460,8 @@ "objectID": "new_pages/importing.html#file-paths", "href": "new_pages/importing.html#file-paths", "title": "7  Import and export", - "section": "7.4 File paths", - "text": "7.4 File paths\nWhen importing or exporting data, you must provide a file path. You can do this one of three ways:\n\nRecommended: provide a “relative” file path with the here package\n\nProvide the “full” / “absolute” file path\n\nManual file selection\n\n\n“Relative” file paths\nIn R, “relative” file paths consist of the file path relative to the root of an R project. They allow for more simple file paths that can work on different computers (e.g. if the R project is on a shared drive or is sent by email). As described above, relative file paths are facilitated by use of the here package.\nAn example of a relative file path constructed with here() is below. We assume the work is in an R project that contains a sub-folder “data” and within that a subfolder “linelists”, in which there is the .xlsx file of interest.\n\nlinelist <- import(here(\"data\", \"linelists\", \"ebola_linelist.xlsx\"))\n\n\n\n“Absolute” file paths\nAbsolute or “full” file paths can be provided to functions like import() but they are “fragile” as they are unique to the user’s specific computer and therefore not recommended.\nBelow is an example of an absolute file path, where in Laura’s computer there is a folder “analysis”, a sub-folder “data” and within that a sub-folder “linelists”, in which there is the .xlsx file of interest.\n\nlinelist <- import(\"C:/Users/Laura/Documents/analysis/data/linelists/ebola_linelist.xlsx\")\n\nA few things to note about absolute file paths:\n\nAvoid using absolute file paths as they will break if the script is run on a different computer\nUse forward slashes (/), as in the example above (note: this is NOT the default for Windows file paths)\n\nFile paths that begin with double slashes (e.g. “//…”) will likely not be recognized by R and will produce an error. Consider moving your work to a “named” or “lettered” drive that begins with a letter (e.g. “J:” or “C:”). See the page on Directory interactions for more details on this issue.\n\nOne scenario where absolute file paths may be appropriate is when you want to import a file from a shared drive that has the same full file path for all users.\nTIP: To quickly convert all \\ to /, highlight the code of interest, use Ctrl+f (in Windows), check the option box for “In selection”, and then use the replace functionality to convert them.\n\n\n\nSelect file manually\nYou can import data manually via one of these methods:\n\nEnvironment RStudio Pane, click “Import Dataset”, and select the type of data\nClick File / Import Dataset / (select the type of data)\n\nTo hard-code manual selection, use the base R command file.choose() (leaving the parentheses empty) to trigger appearance of a pop-up window that allows the user to manually select the file from their computer. For example:\n\n\n# Manual selection of a file. When this command is run, a POP-UP window will appear. \n# The file path selected will be supplied to the import() command.\n\nmy_data <- import(file.choose())\n\nTIP: The pop-up window may appear BEHIND your RStudio window.", + "section": "7.3 File paths", + "text": "7.3 File paths\nWhen importing or exporting data, you must provide a file path. You can do this one of three ways:\n1) Provide the “full” / “absolute” file path (not recommended) 2) Provide a “relative” file path (recommended)\n3) Manual file selection\n\n“Absolute” file paths\nAbsolute or “full” file paths can be provided to functions like import() but they are “fragile” as they are unique to the user’s specific computer and therefore not recommended.\nBelow is an example of a command using an absolute file path. In Laura’s computer there is a folder called “analysis”, a sub-folder “data”, and within that a sub-folder “linelists”, in which there is the .xlsx file of interest.\n\nlinelist <- import(\"C:/Users/Laura/Documents/analysis/data/linelists/linelist_raw.xlsx\")\n\nA few things to note about absolute file paths:\n\nAvoid using absolute file paths as they will not work if the script is run on a different computer\nProvide the file path within quotation marks. It is a “string” (character) value\nUse forward slashes (/), as in the example above (note: this is NOT the default for Windows file paths)\nFile paths that begin with double slashes (e.g. “//…”) will likely not be recognized by R and will produce an error. Consider moving your work to a “named” or “lettered” drive that begins with a letter (e.g. “J:” or “C:”). See the page on Directory interactions for more details on this issue\n\nOne scenario where absolute file paths may be appropriate is when you want to import a file from a shared drive that has the same full file path for all users.\nTIP: To quickly convert all \\ to /, highlight the code of interest, use Ctrl+f (in Windows), check the option box for “In selection”, and then use the replace functionality to convert them.\n\n\nR Projects and “relative” file paths\nIn R, “relative” file paths consist of the file path relative to the root of an R project. This allows for more simple commands which can be run from different computers (e.g. if the R project is on a shared drive or is sent by email).\nLet us assume that our work is in an R project that contains a sub-folder “data” and within that a subfolder “linelists”, in which there is the .xlsx file of interest.\nThe “absolute” file path could be:\n\"C:/Users/Laura/Documents/my_R_project/data/linelists/linelist_raw.xlsx\"\nBy working within an R project, we can import the data by simply writing this command:\n\nlinelist <- import(\"data/linelists/linelist_raw.xlsx\")\n\nBecause we are using an R project, R knows to begin its search for the file in the project’s folder. Then the command tells it to look in the “data” folder, and then the “linelists” folder, and to find the dataset.\n\n\n7.3.1 The here package\nThe importing command can be improved by building the relative file path via the package here and its function here().\nThe file path to the .xlsx file can be created with the below command. Note how each folder after the R project, and the file name itself, is listed within quotation marks and separated by commas.\n\nhere(\"data\", \"linelists\", \"linelist_raw.xlsx\")\n\nBecause this command is run within an R Project, it will return a full, absolute file path that is adapted to the user’s computer, such as below.\n\"C:/Users/Laura/Documents/my_R_project/data/linelists/linelist_raw.xlsx\"\nThe final step is to nest the here() command within the import() function, like this:\n\nlinelist <- import(here(\"data\", \"linelists\", \"linelist_raw.xlsx\"))\n\nThere are several benefits to using here() within import():\n\nhere() becomes very important when creating automated reports with R Markdown or Quarto\n\nhere() free you from worrying about the slash direction (see example above)\n\nTIP: If you are unsure where the “here” root is set to, run the function here() with empty parentheses and then begin building the command.\nRead more about the here package at this link.\n\n\n\nSelect file manually\nYou can import data manually via one of these methods:\n\nEnvironment RStudio Pane, click “Import Dataset”, and select the type of data\nClick File / Import Dataset / (select the type of data)\nTo hard-code manual selection, use the base R command file.choose() (leaving the parentheses empty) to trigger appearance of a pop-up window that allows the user to manually select the file from their computer. For example:\n\n\n# Manual selection of a file. When this command is run, a POP-UP window will appear. \n# The file path selected will be supplied to the import() command.\n\nmy_data <- import(file.choose())\n\nTIP: The pop-up window may appear BEHIND your RStudio window.", "crumbs": [ "Basics", "7  Import and export" @@ -471,8 +471,8 @@ "objectID": "new_pages/importing.html#import-data", "href": "new_pages/importing.html#import-data", "title": "7  Import and export", - "section": "7.5 Import data", - "text": "7.5 Import data\nTo use import() to import a dataset is quite simple. Simply provide the path to the file (including the file name and file extension) in quotes. If using here() to build the file path, follow the instructions above. Below are a few examples:\nImporting a csv file that is located in your “working directory” or in the R project root folder:\n\nlinelist <- import(\"linelist_cleaned.csv\")\n\nImporting the first sheet of an Excel workbook that is located in “data” and “linelists” sub-folders of the R project (the file path built using here()):\n\nlinelist <- import(here(\"data\", \"linelists\", \"linelist_cleaned.xlsx\"))\n\nImporting a data frame (a .rds file) using an absolute file path:\n\nlinelist <- import(\"C:/Users/Laura/Documents/tuberculosis/data/linelists/linelist_cleaned.rds\")\n\n\nSpecific Excel sheets\nBy default, if you provide an Excel workbook (.xlsx) to import(), the workbook’s first sheet will be imported. If you want to import a specific sheet, include the sheet name to the which = argument. For example:\n\nmy_data <- import(\"my_excel_file.xlsx\", which = \"Sheetname\")\n\nIf using the here() method to provide a relative pathway to import(), you can still indicate a specific sheet by adding the which = argument after the closing parentheses of the here() function.\n\n# Demonstration: importing a specific Excel sheet when using relative pathways with the 'here' package\nlinelist_raw <- import(here(\"data\", \"linelist.xlsx\"), which = \"Sheet1\")` \n\nTo export a data frame from R to a specific Excel sheet and have the rest of the Excel workbook remain unchanged, you will have to import, edit, and export with an alternative package catered to this purpose such as openxlsx. See more information in the page on Directory interactions or at this github page.\nIf your Excel workbook is .xlsb (binary format Excel workbook) you may not be able to import it using rio. Consider re-saving it as .xlsx, or using a package like readxlsb which is built for this purpose.\n\n\n\nMissing values\nYou may want to designate which value(s) in your dataset should be considered as missing. As explained in the page on Missing data, the value in R for missing data is NA, but perhaps the dataset you want to import uses 99, “Missing”, or just empty character space “” instead.\nUse the na = argument for import() and provide the value(s) within quotes (even if they are numbers). You can specify multiple values by including them within a vector, using c() as shown below.\nHere, the value “99” in the imported dataset is considered missing and converted to NA in R.\n\nlinelist <- import(here(\"data\", \"my_linelist.xlsx\"), na = \"99\")\n\nHere, any of the values “Missing”, “” (empty cell), or ” ” (single space) in the imported dataset are converted to NA in R.\n\nlinelist <- import(here(\"data\", \"my_linelist.csv\"), na = c(\"Missing\", \"\", \" \"))\n\n\n\n\nSkip rows\nSometimes, you may want to avoid importing a row of data. You can do this with the argument skip = if using import() from rio on a .xlsx or .csv file. Provide the number of rows you want to skip.\n\nlinelist_raw <- import(\"linelist_raw.xlsx\", skip = 1) # does not import header row\n\nUnfortunately skip = only accepts one integer value, not a range (e.g. “2:10” does not work). To skip import of specific rows that are not consecutive from the top, consider importing multiple times and using bind_rows() from dplyr. See the example below of skipping only row 2.\n\n\nManage a second header row\nSometimes, your data may have a second row, for example if it is a “data dictionary” row as shown below. This situation can be problematic because it can result in all columns being imported as class “character”.\nBelow is an example of this kind of dataset (with the first row being the data dictionary).\n\n\n\n\n\n\n\nRemove the second header row\nTo drop the second header row, you will likely need to import the data twice.\n\nImport the data in order to store the correct column names\n\nImport the data again, skipping the first two rows (header and second rows)\n\nBind the correct names onto the reduced dataframe\n\nThe exact argument used to bind the correct column names depends on the type of data file (.csv, .tsv, .xlsx, etc.). This is because rio is using a different function for the different file types (see table above).\nFor Excel files: (col_names =)\n\n# import first time; store the column names\nlinelist_raw_names <- import(\"linelist_raw.xlsx\") %>% names() # save true column names\n\n# import second time; skip row 2, and assign column names to argument col_names =\nlinelist_raw <- import(\"linelist_raw.xlsx\",\n skip = 2,\n col_names = linelist_raw_names\n ) \n\nFor CSV files: (col.names =)\n\n# import first time; sotre column names\nlinelist_raw_names <- import(\"linelist_raw.csv\") %>% names() # save true column names\n\n# note argument for csv files is 'col.names = '\nlinelist_raw <- import(\"linelist_raw.csv\",\n skip = 2,\n col.names = linelist_raw_names\n ) \n\nBackup option - changing column names as a separate command\n\n# assign/overwrite headers using the base 'colnames()' function\ncolnames(linelist_raw) <- linelist_raw_names\n\n\n\nMake a data dictionary\nBonus! If you do have a second row that is a data dictionary, you can easily create a proper data dictionary from it. This tip is adapted from this post.\n\ndict <- linelist_2headers %>% # begin: linelist with dictionary as first row\n head(1) %>% # keep only column names and first dictionary row \n pivot_longer(cols = everything(), # pivot all columns to long format\n names_to = \"Column\", # assign new column names\n values_to = \"Description\")\n\n\n\n\n\n\n\n\n\nCombine the two header rows\nIn some cases when your raw dataset has two header rows (or more specifically, the 2nd row of data is a secondary header), you may want to “combine” them or add the values in the second header row into the first header row.\nThe command below will define the data frame’s column names as the combination (pasting together) of the first (true) headers with the value immediately underneath (in the first row).\n\nnames(my_data) <- paste(names(my_data), my_data[1, ], sep = \"_\")\n\n\n\n\n\nGoogle sheets\nYou can import data from an online Google spreadsheet with the googlesheet4 package and by authenticating your access to the spreadsheet.\n\npacman::p_load(\"googlesheets4\")\n\nBelow, a demo Google sheet is imported and saved. This command may prompt confirmation of authentification of your Google account. Follow prompts and pop-ups in your internet browser to grant Tidyverse API packages permissions to edit, create, and delete your spreadsheets in Google Drive.\nThe sheet below is “viewable for anyone with the link” and you can try to import it.\n\nGsheets_demo <- read_sheet(\"https://docs.google.com/spreadsheets/d/1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY/edit#gid=0\")\n\nThe sheet can also be imported using only the sheet ID, a shorter part of the URL:\n\nGsheets_demo <- read_sheet(\"1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY\")\n\nAnother package, googledrive offers useful functions for writing, editing, and deleting Google sheets. For example, using the gs4_create() and sheet_write() functions found in this package.\nHere are some other helpful online tutorials:\nbasic Google sheets importing tutorial\nmore detailed tutorial\ninteraction between the googlesheets4 and tidyverse", + "section": "7.4 Import data", + "text": "7.4 Import data\nTo use import() to import a dataset is quite simple. Simply provide the path to the file (including the file name and file extension) in quotes. If using here() to build the file path, follow the instructions above. Below are a few examples:\nImporting a csv file that is located in your “working directory” or in the R project root folder:\n\nlinelist <- import(\"linelist_cleaned.csv\")\n\nImporting the first sheet of an Excel workbook that is located in “data” and “linelists” sub-folders of the R project (the file path built using here()):\n\nlinelist <- import(here(\"data\", \"linelists\", \"linelist_cleaned.xlsx\"))\n\nImporting a data frame (a .rds file) using an absolute file path:\n\nlinelist <- import(\"C:/Users/Laura/Documents/tuberculosis/data/linelists/linelist_cleaned.rds\")\n\n\nSpecific Excel sheets\nBy default, if you provide an Excel workbook (.xlsx) to import(), the workbook’s first sheet will be imported. If you want to import a specific sheet, include the sheet name to the which = argument. For example:\n\nmy_data <- import(\"my_excel_file.xlsx\", which = \"Sheetname\")\n\nIf using the here() method to create the file path, you can still indicate a specific sheet by adding the which = argument after the closing parentheses of the here() function.\n\n# Demonstration: importing a specific Excel sheet when using relative pathways with the 'here' package\nlinelist_raw <- import(here(\"data\", \"linelist.xlsx\"), which = \"Sheet1\")` \n\nTo export a data frame from R to a specific Excel sheet and have the rest of the Excel workbook remain unchanged, you will have to import, edit, and export with an alternative package catered to this purpose such as openxlsx. See more information in the page on Directory interactions or at this github page.\nIf your Excel workbook is .xlsb (binary format Excel workbook) you may not be able to import it using rio. Consider re-saving it as .xlsx, or using a package like readxlsb which is built for this purpose.\n\n\n\nMissing values\nYou may want to designate which value(s) in your dataset should be considered as missing. As explained in the page on Missing data, the value in R for missing data is NA, but perhaps the dataset you want to import uses 99, “Missing”, or just empty character space “” instead.\nUse the na = argument for import() and provide the value(s) within quotes (even if they are numbers). You can specify multiple values by including them within a vector, using c() as shown below.\nHere, the value “99” in the imported dataset is considered missing and converted to NA in R.\n\nlinelist <- import(here(\"data\", \"my_linelist.xlsx\"), na = \"99\")\n\nHere, any of the values “Missing”, “” (empty cell), or ” ” (single space) in the imported dataset are converted to NA in R.\n\nlinelist <- import(here(\"data\", \"my_linelist.csv\"), na = c(\"Missing\", \"\", \" \"))\n\n\n\n\nSkip rows\nSometimes, you may want to avoid importing a row of data. You can do this with the argument skip = if using import() from rio on a .xlsx or .csv file. Provide the number of rows you want to skip.\n\nlinelist_raw <- import(\"linelist_raw.xlsx\", skip = 1) # does not import header row\n\nUnfortunately skip = only accepts one integer value, not a range (e.g. “2:10” does not work). To skip import of specific rows that are not consecutive from the top, consider importing multiple times and using bind_rows() from dplyr. See the example below of skipping only row 2.\n\n\nManage a second header row\nSometimes, your data may have a second row, for example if it is a “data dictionary” row as shown below. This situation can be problematic because it can result in all columns being imported as class “character”.\nBelow is an example of this kind of dataset (with the first row being the data dictionary).\n\n\n\n\n\n\n\nRemove the second header row\nTo drop the second header row, you will likely need to import the data twice.\n\nImport the data in order to store the correct column names\n\nImport the data again, skipping the first two rows (header and second rows)\n\nBind the correct names onto the reduced dataframe\n\nThe exact argument used to bind the correct column names depends on the type of data file (.csv, .tsv, .xlsx, etc.). This is because rio is using a different function for the different file types (see table above).\nFor Excel files: (col_names =)\n\n# import first time; store the column names\nlinelist_raw_names <- import(\"linelist_raw.xlsx\") %>% \n names() # save true column names\n\n# import second time; skip row 2, and assign column names to argument col_names =\nlinelist_raw <- import(\"linelist_raw.xlsx\",\n skip = 2,\n col_names = linelist_raw_names\n ) \n\nFor CSV files: (col.names =)\n\n# import first time; save column names\nlinelist_raw_names <- import(\"linelist_raw.csv\") %>% \n names() # save true column names\n\n# note argument for csv files is 'col.names = '\nlinelist_raw <- import(\"linelist_raw.csv\",\n skip = 2,\n col.names = linelist_raw_names\n ) \n\nBackup option - changing column names as a separate command\n\n# assign/overwrite headers using the base 'colnames()' function\ncolnames(linelist_raw) <- linelist_raw_names\n\n\n\nMake a data dictionary\nBonus! If you do have a second row that is a data dictionary, you can easily create a proper data dictionary from it. This tip is adapted from this post.\n\ndict <- linelist_2headers %>% # begin: linelist with dictionary as first row\n head(1) %>% # keep only column names and first dictionary row \n pivot_longer(cols = everything(), # pivot all columns to long format\n names_to = \"Column\", # assign new column names\n values_to = \"Description\")\n\n\n\n\n\n\n\n\n\nCombine the two header rows\nIn some cases when your raw dataset has two header rows (or more specifically, the 2nd row of data is a secondary header), you may want to “combine” them or add the values in the second header row into the first header row.\nThe command below will define the data frame’s column names as the combination (pasting together) of the first (true) headers with the value immediately underneath (in the first row).\n\nnames(my_data) <- paste(names(my_data), my_data[1, ], sep = \"_\")\n\n\n\n\n\nGoogle sheets\nYou can import data from an online Google spreadsheet with the googlesheet4 package and by authenticating your access to the spreadsheet.\n\npacman::p_load(\"googlesheets4\")\n\nBelow, a demo Google sheet is imported and saved. This command may prompt confirmation of authentification of your Google account. Follow prompts and pop-ups in your internet browser to grant Tidyverse API packages permissions to edit, create, and delete your spreadsheets in Google Drive.\nThe sheet below is “viewable for anyone with the link” and you can try to import it.\n\nGsheets_demo <- read_sheet(\"https://docs.google.com/spreadsheets/d/1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY/edit#gid=0\")\n\nThe sheet can also be imported using only the sheet ID, a shorter part of the URL:\n\nGsheets_demo <- read_sheet(\"1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY\")\n\nAnother package, googledrive offers useful functions for writing, editing, and deleting Google sheets. For example, using the gs4_create() and sheet_write() functions found in this package.\nHere are some other helpful online tutorials:\nGoogle sheets importing tutorial.\nMore detailed tutorial.\nInteraction between the googlesheets4 and tidyverse.\nAdditionally, you can also use import from the rio package.\n\nGsheets_demo <- rio(\"https://docs.google.com/spreadsheets/d/1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY/edit#gid=0\")", "crumbs": [ "Basics", "7  Import and export" @@ -482,8 +482,8 @@ "objectID": "new_pages/importing.html#multiple-files---import-export-split-combine", "href": "new_pages/importing.html#multiple-files---import-export-split-combine", "title": "7  Import and export", - "section": "7.6 Multiple files - import, export, split, combine", - "text": "7.6 Multiple files - import, export, split, combine\nSee the page on Iteration, loops, and lists for examples of how to import and combine multiple files, or multiple Excel workbook files. That page also has examples on how to split a data frame into parts and export each one separately, or as named sheets in an Excel workbook.", + "section": "7.5 Multiple files - import, export, split, combine", + "text": "7.5 Multiple files - import, export, split, combine\nSee the page on Iteration, loops, and lists for examples of how to import and combine multiple files, or multiple Excel workbook files.\nThat page also has examples on how to split a data frame into parts and export each one separately, or as named sheets in an Excel workbook.", "crumbs": [ "Basics", "7  Import and export" @@ -493,8 +493,8 @@ "objectID": "new_pages/importing.html#import_github", "href": "new_pages/importing.html#import_github", "title": "7  Import and export", - "section": "7.7 Import from Github", - "text": "7.7 Import from Github\nImporting data directly from Github into R can be very easy or can require a few steps - depending on the file type. Below are some approaches:\n\nCSV files\nIt can be easy to import a .csv file directly from Github into R with an R command.\n\nGo to the Github repo, locate the file of interest, and click on it\n\nClick on the “Raw” button (you will then see the “raw” csv data, as shown below)\n\nCopy the URL (web address)\n\nPlace the URL in quotes within the import() R command\n\n\n\n\n\n\n\n\n\n\n\n\nXLSX files\nYou may not be able to view the “Raw” data for some files (e.g. .xlsx, .rds, .nwk, .shp)\n\nGo to the Github repo, locate the file of interest, and click on it\n\nClick the “Download” button, as shown below\n\nSave the file on your computer, and import it into R\n\n\n\n\n\n\n\n\n\n\n\n\nShapefiles\nShapefiles have many sub-component files, each with a different file extention. One file will have the “.shp” extension, but others may have “.dbf”, “.prj”, etc. To download a shapefile from Github, you will need to download each of the sub-component files individually, and save them in the same folder on your computer. In Github, click on each file individually and download them by clicking on the “Download” button.\nOnce saved to your computer you can import the shapefile as shown in the GIS basics page using st_read() from the sf package. You only need to provide the filepath and name of the “.shp” file - as long as the other related files are within the same folder on your computer.\nBelow, you can see how the shapefile “sle_adm3” consists of many files - each of which must be downloaded from Github.", + "section": "7.6 Import from Github", + "text": "7.6 Import from Github\nImporting data directly from Github into R can be very easy or can require a few steps - depending on the file type. Below are some approaches:\n\nCSV files\nIt can be easy to import a .csv file directly from Github into R with an R command.\n\nGo to the Github repo, locate the file of interest, and click on it\nClick on the “Raw” button (you will then see the “raw” csv data, as shown below)\nCopy the URL (web address)\nPlace the URL in quotes within the import() R command\n\n\n\n\n\n\n\n\n\n\n\n\nXLSX files\nYou may not be able to view the “Raw” data for some files (e.g. .xlsx, .rds, .nwk, .shp)\n\nGo to the Github repo, locate the file of interest, and click on it\n\nClick the “Download” button, as shown below\n\nSave the file on your computer, and import it into R\n\n\n\n\n\n\n\n\n\n\n\n\nShapefiles\nShapefiles have many sub-component files, each with a different file extention. One file will have the “.shp” extension, but others may have “.dbf”, “.prj”, etc. To download a shapefile from Github, you will need to download each of the sub-component files individually, and save them in the same folder on your computer. In Github, click on each file individually and download them by clicking on the “Download” button.\nOnce saved to your computer you can import the shapefile as shown in the GIS basics page using st_read() from the sf package. You only need to provide the filepath and name of the “.shp” file - as long as the other related files are within the same folder on your computer.\nBelow, you can see how the shapefile “sle_adm3” consists of many files - each of which must be downloaded from Github.", "crumbs": [ "Basics", "7  Import and export" @@ -504,8 +504,8 @@ "objectID": "new_pages/importing.html#manual-data-entry", "href": "new_pages/importing.html#manual-data-entry", "title": "7  Import and export", - "section": "7.8 Manual data entry", - "text": "7.8 Manual data entry\n\nEntry by rows\nUse the tribble function from the tibble package from the tidyverse (online tibble reference).\nNote how column headers start with a tilde (~). Also note that each column must contain only one class of data (character, numeric, etc.). You can use tabs, spacing, and new rows to make the data entry more intuitive and readable. Spaces do not matter between values, but each row is represented by a new line of code. For example:\n\n# create the dataset manually by row\nmanual_entry_rows <- tibble::tribble(\n ~colA, ~colB,\n \"a\", 1,\n \"b\", 2,\n \"c\", 3\n )\n\nAnd now we display the new dataset:\n\n\n\n\n\n\n\n\nEntry by columns\nSince a data frame consists of vectors (vertical columns), the base approach to manual dataframe creation in R expects you to define each column and then bind them together. This can be counter-intuitive in epidemiology, as we usually think about our data in rows (as above).\n\n# define each vector (vertical column) separately, each with its own name\nPatientID <- c(235, 452, 778, 111)\nTreatment <- c(\"Yes\", \"No\", \"Yes\", \"Yes\")\nDeath <- c(1, 0, 1, 0)\n\nCAUTION: All vectors must be the same length (same number of values).\nThe vectors can then be bound together using the function data.frame():\n\n# combine the columns into a data frame, by referencing the vector names\nmanual_entry_cols <- data.frame(PatientID, Treatment, Death)\n\nAnd now we display the new dataset:\n\n\n\n\n\n\n\n\nPasting from clipboard\nIf you copy data from elsewhere and have it on your clipboard, you can try one of the two ways below:\nFrom the clipr package, you can use read_clip_tbl() to import as a data frame, or just just read_clip() to import as a character vector. In both cases, leave the parentheses empty.\n\nlinelist <- clipr::read_clip_tbl() # imports current clipboard as data frame\nlinelist <- clipr::read_clip() # imports as character vector\n\nYou can also easily export to your system’s clipboard with clipr. See the section below on Export.\nAlternatively, you can use the the read.table() function from base R with file = \"clipboard\") to import as a data frame:\n\ndf_from_clipboard <- read.table(\n file = \"clipboard\", # specify this as \"clipboard\"\n sep = \"t\", # separator could be tab, or commas, etc.\n header=TRUE) # if there is a header row", + "section": "7.7 Manual data entry", + "text": "7.7 Manual data entry\n\nEntry by rows\nUse the tribble function from the tibble package from the tidyverse (online tibble reference).\nNote how column headers start with a tilde (~). Also note that each column must contain only one class of data (character, numeric, etc.). You can use tabs, spacing, and new rows to make the data entry more intuitive and readable. Spaces do not matter between values, but each row is represented by a new line of code. For example:\n\n# create the dataset manually by row\nmanual_entry_rows <- tibble::tribble(\n ~colA, ~colB,\n \"a\", 1,\n \"b\", 2,\n \"c\", 3\n )\n\nAnd now we display the new dataset:\n\n\n\n\n\n\n\n\nEntry by columns\nSince a data frame consists of vectors (vertical columns), the base approach to manual dataframe creation in R expects you to define each column and then bind them together. This can be counter-intuitive in epidemiology, as we usually think about our data in rows (as above).\n\n# define each vector (vertical column) separately, each with its own name\nPatientID <- c(235, 452, 778, 111)\nTreatment <- c(\"Yes\", \"No\", \"Yes\", \"Yes\")\nDeath <- c(1, 0, 1, 0)\n\nCAUTION: All vectors must be the same length (same number of values).\nThe vectors can then be bound together using the function data.frame():\n\n# combine the columns into a data frame, by referencing the vector names\nmanual_entry_cols <- data.frame(PatientID, Treatment, Death)\n\nAnd now we display the new dataset:\n\n\n\n\n\n\n\n\nPasting from clipboard\nIf you copy data from elsewhere and have it on your clipboard, you can try one of the two ways below:\nFrom the clipr package, you can use read_clip_tbl() to import as a data frame, or just just read_clip() to import as a character vector. In both cases, leave the parentheses empty.\n\nlinelist <- clipr::read_clip_tbl() # imports current clipboard as data frame\nlinelist <- clipr::read_clip() # imports as character vector\n\nYou can also easily export to your system’s clipboard with clipr. See the section below on Export.\nAlternatively, you can use the the read.table() function from base R with file = \"clipboard\") to import as a data frame:\n\ndf_from_clipboard <- read.table(\n file = \"clipboard\", # specify this as \"clipboard\"\n sep = \"t\", # separator could be tab, or commas, etc.\n header=TRUE) # if there is a header row", "crumbs": [ "Basics", "7  Import and export" @@ -515,8 +515,8 @@ "objectID": "new_pages/importing.html#import-most-recent-file", "href": "new_pages/importing.html#import-most-recent-file", "title": "7  Import and export", - "section": "7.9 Import most recent file", - "text": "7.9 Import most recent file\nOften you may receive daily updates to your datasets. In this case you will want to write code that imports the most recent file. Below we present two ways to approach this:\n\nSelecting the file based on the date in the file name\n\nSelecting the file based on file metadata (last modification)\n\n\nDates in file name\nThis approach depends on three premises:\n\nYou trust the dates in the file names\n\nThe dates are numeric and appear in generally the same format (e.g. year then month then day)\n\nThere are no other numbers in the file name\n\nWe will explain each step, and then show you them combined at the end.\nFirst, use dir() from base R to extract just the file names for each file in the folder of interest. See the page on Directory interactions for more details about dir(). In this example, the folder of interest is the folder “linelists” within the folder “example” within “data” within the R project.\n\nlinelist_filenames <- dir(here(\"data\", \"example\", \"linelists\")) # get file names from folder\nlinelist_filenames # print\n\n[1] \"20201007linelist.csv\" \"case_linelist_2020-10-02.csv\" \n[3] \"case_linelist_2020-10-03.csv\" \"case_linelist_2020-10-04.csv\" \n[5] \"case_linelist_2020-10-05.csv\" \"case_linelist_2020-10-08.xlsx\"\n[7] \"case_linelist20201006.csv\" \n\n\nOnce you have this vector of names, you can extract the dates from them by applying str_extract() from stringr using this regular expression. It extracts any numbers in the file name (including any other characters in the middle such as dashes or slashes). You can read more about stringr in the Strings and characters page.\n\nlinelist_dates_raw <- stringr::str_extract(linelist_filenames, \"[0-9].*[0-9]\") # extract numbers and any characters in between\nlinelist_dates_raw # print\n\n[1] \"20201007\" \"2020-10-02\" \"2020-10-03\" \"2020-10-04\" \"2020-10-05\"\n[6] \"2020-10-08\" \"20201006\" \n\n\nAssuming the dates are written in generally the same date format (e.g. Year then Month then Day) and the years are 4-digits, you can use lubridate’s flexible conversion functions (ymd(), dmy(), or mdy()) to convert them to dates. For these functions, the dashes, spaces, or slashes do not matter, only the order of the numbers. Read more in the Working with dates page.\n\nlinelist_dates_clean <- lubridate::ymd(linelist_dates_raw)\nlinelist_dates_clean\n\n[1] \"2020-10-07\" \"2020-10-02\" \"2020-10-03\" \"2020-10-04\" \"2020-10-05\"\n[6] \"2020-10-08\" \"2020-10-06\"\n\n\nThe base R function which.max() can then be used to return the index position (e.g. 1st, 2nd, 3rd, …) of the maximum date value. The latest file is correctly identified as the 6th file - “case_linelist_2020-10-08.xlsx”.\n\nindex_latest_file <- which.max(linelist_dates_clean)\nindex_latest_file\n\n[1] 6\n\n\nIf we condense all these commands, the complete code could look like below. Note that the . in the last line is a placeholder for the piped object at that point in the pipe sequence. At that point the value is simply the number 6. This is placed in double brackets to extract the 6th element of the vector of file names produced by dir().\n\n# load packages\npacman::p_load(\n tidyverse, # data management\n stringr, # work with strings/characters\n lubridate, # work with dates\n rio, # import / export\n here, # relative file paths\n fs) # directory interactions\n\n# extract the file name of latest file\nlatest_file <- dir(here(\"data\", \"example\", \"linelists\")) %>% # file names from \"linelists\" sub-folder \n str_extract(\"[0-9].*[0-9]\") %>% # pull out dates (numbers)\n ymd() %>% # convert numbers to dates (assuming year-month-day format)\n which.max() %>% # get index of max date (latest file)\n dir(here(\"data\", \"example\", \"linelists\"))[[.]] # return the filename of latest linelist\n\nlatest_file # print name of latest file\n\n[1] \"case_linelist_2020-10-08.xlsx\"\n\n\nYou can now use this name to finish the relative file path, with here():\n\nhere(\"data\", \"example\", \"linelists\", latest_file) \n\nAnd you can now import the latest file:\n\n# import\nimport(here(\"data\", \"example\", \"linelists\", latest_file)) # import \n\n\n\nUse the file info\nIf your files do not have dates in their names (or you do not trust those dates), you can try to extract the last modification date from the file metadata. Use functions from the package fs to examine the metadata information for each file, which includes the last modification time and the file path.\nBelow, we provide the folder of interest to fs’s dir_info(). In this case, the folder of interest is in the R project in the folder “data”, the sub-folder “example”, and its sub-folder “linelists”. The result is a data frame with one line per file and columns for modification_time, path, etc. You can see a visual example of this in the page on Directory interactions.\nWe can sort this data frame of files by the column modification_time, and then keep only the top/latest row (file) with base R’s head(). Then we can extract the file path of this latest file only with the dplyr function pull() on the column path. Finally we can pass this file path to import(). The imported file is saved as latest_file.\n\nlatest_file <- dir_info(here(\"data\", \"example\", \"linelists\")) %>% # collect file info on all files in directory\n arrange(desc(modification_time)) %>% # sort by modification time\n head(1) %>% # keep only the top (latest) file\n pull(path) %>% # extract only the file path\n import() # import the file", + "section": "7.8 Import most recent file", + "text": "7.8 Import most recent file\nOften you may receive daily updates to your datasets. In this case you will want to write code that imports the most recent file. Below we present two ways to approach this:\n\nSelecting the file based on the date in the file name\n\nSelecting the file based on file metadata (last modification)\n\n\nDates in file name\nThis approach depends on three premises:\n\nYou trust the dates in the file names\nThe dates are numeric and appear in generally the same format (e.g. year then month then day)\nThere are no other numbers in the file name\n\nWe will explain each step, and then show you them combined at the end.\nFirst, use dir() from base R to extract just the file names for each file in the folder of interest. See the page on Directory interactions for more details about dir(). In this example, the folder of interest is the folder “linelists” within the folder “example” within “data” within the R project.\n\nlinelist_filenames <- dir(here(\"data\", \"example\", \"linelists\")) # get file names from folder\nlinelist_filenames # print\n\n[1] \"20201007linelist.csv\" \"case_linelist_2020-10-02.csv\" \n[3] \"case_linelist_2020-10-03.csv\" \"case_linelist_2020-10-04.csv\" \n[5] \"case_linelist_2020-10-05.csv\" \"case_linelist_2020-10-08.xlsx\"\n[7] \"case_linelist20201006.csv\" \n\n\nOnce you have this vector of names, you can extract the dates from them by applying str_extract() from stringr using this regular expression. It extracts any numbers in the file name (including any other characters in the middle such as dashes or slashes). You can read more about stringr in the Strings and characters page.\n\nlinelist_dates_raw <- stringr::str_extract(linelist_filenames, \"[0-9].*[0-9]\") # extract numbers and any characters in between\nlinelist_dates_raw # print\n\n[1] \"20201007\" \"2020-10-02\" \"2020-10-03\" \"2020-10-04\" \"2020-10-05\"\n[6] \"2020-10-08\" \"20201006\" \n\n\nAssuming the dates are written in generally the same date format (e.g. Year then Month then Day) and the years are 4-digits, you can use lubridate’s flexible conversion functions (ymd(), dmy(), or mdy()) to convert them to dates. For these functions, the dashes, spaces, or slashes do not matter, only the order of the numbers. Read more in the Working with dates page.\n\nlinelist_dates_clean <- lubridate::ymd(linelist_dates_raw)\nlinelist_dates_clean\n\n[1] \"2020-10-07\" \"2020-10-02\" \"2020-10-03\" \"2020-10-04\" \"2020-10-05\"\n[6] \"2020-10-08\" \"2020-10-06\"\n\n\nThe base R function which.max() can then be used to return the index position (e.g. 1st, 2nd, 3rd, …) of the maximum date value. The latest file is correctly identified as the 6th file - “case_linelist_2020-10-08.xlsx”.\n\nindex_latest_file <- which.max(linelist_dates_clean)\nindex_latest_file\n\n[1] 6\n\n\nIf we condense all these commands, the complete code could look like below. Note that the . in the last line is a placeholder for the piped object at that point in the pipe sequence. At that point the value is simply the number 6. This is placed in double brackets to extract the 6th element of the vector of file names produced by dir().\n\n# load packages\npacman::p_load(\n tidyverse, # data management\n stringr, # work with strings/characters\n lubridate, # work with dates\n rio, # import / export\n here, # relative file paths\n fs) # directory interactions\n\n# extract the file name of latest file\nlatest_file <- dir(here(\"data\", \"example\", \"linelists\")) %>% # file names from \"linelists\" sub-folder \n str_extract(\"[0-9].*[0-9]\") %>% # pull out dates (numbers)\n ymd() %>% # convert numbers to dates (assuming year-month-day format)\n which.max() %>% # get index of max date (latest file)\n dir(here(\"data\", \"example\", \"linelists\"))[[.]] # return the filename of latest linelist\n\nlatest_file # print name of latest file\n\n[1] \"case_linelist_2020-10-08.xlsx\"\n\n\nYou can now use this name to finish the relative file path, with here():\n\nhere(\"data\", \"example\", \"linelists\", latest_file) \n\nAnd you can now import the latest file:\n\n# import\nimport(here(\"data\", \"example\", \"linelists\", latest_file)) # import \n\n\n\nUse the file info\nIf your files do not have dates in their names (or you do not trust those dates), you can try to extract the last modification date from the file metadata. Use functions from the package fs to examine the metadata information for each file, which includes the last modification time and the file path.\nBelow, we provide the folder of interest to fs’s dir_info(). In this case, the folder of interest is in the R project in the folder “data”, the sub-folder “example”, and its sub-folder “linelists”. The result is a data frame with one line per file and columns for modification_time, path, etc. You can see a visual example of this in the page on Directory interactions.\nWe can sort this data frame of files by the column modification_time, and then keep only the top/latest row (file) with base R’s head(). Then we can extract the file path of this latest file only with the dplyr function pull() on the column path. Finally we can pass this file path to import(). The imported file is saved as latest_file.\n\nlatest_file <- dir_info(here(\"data\", \"example\", \"linelists\")) %>% # collect file info on all files in directory\n arrange(desc(modification_time)) %>% # sort by modification time\n head(1) %>% # keep only the top (latest) file\n pull(path) %>% # extract only the file path\n import() # import the file", "crumbs": [ "Basics", "7  Import and export" @@ -526,8 +526,8 @@ "objectID": "new_pages/importing.html#import_api", "href": "new_pages/importing.html#import_api", "title": "7  Import and export", - "section": "7.10 APIs", - "text": "7.10 APIs\nAn “Automated Programming Interface” (API) can be used to directly request data from a website. APIs are a set of rules that allow one software application to interact with another. The client (you) sends a “request” and receives a “response” containing content. The R packages httr and jsonlite can facilitate this process.\nEach API-enabled website will have its own documentation and specifics to become familiar with. Some sites are publicly available and can be accessed by anyone. Others, such as platforms with user IDs and credentials, require authentication to access their data.\nNeedless to say, it is necessary to have an internet connection to import data via API. We will briefly give examples of use of APIs to import data, and link you to further resources.\nNote: recall that data may be posted* on a website without an API, which may be easier to retrieve. For example a posted CSV file may be accessible simply by providing the site URL to import() as described in the section on importing from Github.*\n\nHTTP request\nThe API exchange is most commonly done through an HTTP request. HTTP is Hypertext Transfer Protocol, and is the underlying format of a request/response between a client and a server. The exact input and output may vary depending on the type of API but the process is the same - a “Request” (often HTTP Request) from the user, often containing a query, followed by a “Response”, containing status information about the request and possibly the requested content.\nHere are a few components of an HTTP request:\n\nThe URL of the API endpoint\n\nThe “Method” (or “Verb”)\n\nHeaders\n\nBody\n\nThe HTTP request “method” is the action your want to perform. The two most common HTTP methods are GET and POST but others could include PUT, DELETE, PATCH, etc. When importing data into R it is most likely that you will use GET.\nAfter your request, your computer will receive a “response” in a format similar to what you sent, including URL, HTTP status (Status 200 is what you want!), file type, size, and the desired content. You will then need to parse this response and turn it into a workable data frame within your R environment.\n\n\nPackages\nThe httr package works well for handling HTTP requests in R. It requires little prior knowledge of Web APIs and can be used by people less familiar with software development terminology. In addition, if the HTTP response is .json, you can use jsonlite to parse the response.\n\n# load packages\npacman::p_load(httr, jsonlite, tidyverse)\n\n\n\nPublicly-available data\nBelow is an example of an HTTP request, borrowed from a tutorial from the Trafford Data Lab. This site has several other resources to learn and API exercises.\nScenario: We want to import a list of fast food outlets in the city of Trafford, UK. The data can be accessed from the API of the Food Standards Agency, which provides food hygiene rating data for the United Kingdom.\nHere are the parameters for our request:\n\nHTTP verb: GET\n\nAPI endpoint URL: http://api.ratings.food.gov.uk/Establishments\n\nSelected parameters: name, address, longitude, latitude, businessTypeId, ratingKey, localAuthorityId\n\nHeaders: “x-api-version”, 2\n\nData format(s): JSON, XML\n\nDocumentation: http://api.ratings.food.gov.uk/help\n\nThe R code would be as follows:\n\n# prepare the request\npath <- \"http://api.ratings.food.gov.uk/Establishments\"\nrequest <- GET(url = path,\n query = list(\n localAuthorityId = 188,\n BusinessTypeId = 7844,\n pageNumber = 1,\n pageSize = 5000),\n add_headers(\"x-api-version\" = \"2\"))\n\n# check for any server error (\"200\" is good!)\nrequest$status_code\n\n# submit the request, parse the response, and convert to a data frame\nresponse <- content(request, as = \"text\", encoding = \"UTF-8\") %>%\n fromJSON(flatten = TRUE) %>%\n pluck(\"establishments\") %>%\n as_tibble()\n\nYou can now clean and use the response data frame, which contains one row per fast food facility.\n\n\nAuthentication required\nSome APIs require authentication - for you to prove who you are, so you can access restricted data. To import these data, you may need to first use a POST method to provide a username, password, or code. This will return an access token, that can be used for subsequent GET method requests to retrieve the desired data.\nBelow is an example of querying data from Go.Data, which is an outbreak investigation tool. Go.Data uses an API for all interactions between the web front-end and smartphone applications used for data collection. Go.Data is used throughout the world. Because outbreak data are sensitive and you should only be able to access data for your outbreak, authentication is required.\nBelow is some sample R code using httr and jsonlite for connecting to the Go.Data API to import data on contact follow-up from your outbreak.\n\n# set credentials for authorization\nurl <- \"https://godatasampleURL.int/\" # valid Go.Data instance url\nusername <- \"username\" # valid Go.Data username \npassword <- \"password\" # valid Go,Data password \noutbreak_id <- \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxx\" # valid Go.Data outbreak ID\n\n# get access token\nurl_request <- paste0(url,\"api/oauth/token?access_token=123\") # define base URL request\n\n# prepare request\nresponse <- POST(\n url = url_request, \n body = list(\n username = username, # use saved username/password from above to authorize \n password = password), \n encode = \"json\")\n\n# execute request and parse response\ncontent <-\n content(response, as = \"text\") %>%\n fromJSON(flatten = TRUE) %>% # flatten nested JSON\n glimpse()\n\n# Save access token from response\naccess_token <- content$access_token # save access token to allow subsequent API calls below\n\n# import outbreak contacts\n# Use the access token \nresponse_contacts <- GET(\n paste0(url,\"api/outbreaks/\",outbreak_id,\"/contacts\"), # GET request\n add_headers(\n Authorization = paste(\"Bearer\", access_token, sep = \" \")))\n\njson_contacts <- content(response_contacts, as = \"text\") # convert to text JSON\n\ncontacts <- as_tibble(fromJSON(json_contacts, flatten = TRUE)) # flatten JSON to tibble\n\nCAUTION: If you are importing large amounts of data from an API requiring authentication, it may time-out. To avoid this, retrieve access_token again before each API GET request and try using filters or limits in the query. \nTIP: The fromJSON() function in the jsonlite package does not fully un-nest the first time it’s executed, so you will likely still have list items in your resulting tibble. You will need to further un-nest for certain variables; depending on how nested your .json is. To view more info on this, view the documentation for the jsonlite package, such as the flatten() function. \nFor more details, View documentation on LoopBack Explorer, the Contact Tracing page or API tips on Go.Data Github repository\nYou can read more about the httr package here\nThis section was also informed by this tutorial and this tutorial.", + "section": "7.9 APIs", + "text": "7.9 APIs\nAn “Automated Programming Interface” (API) can be used to directly request data from a website. APIs are a set of rules that allow one software application to interact with another. The client (you) sends a “request” and receives a “response” containing content. The R packages httr and jsonlite can facilitate this process.\nEach API-enabled website will have its own documentation and specifics to become familiar with. Some sites are publicly available and can be accessed by anyone. Others, such as platforms with user IDs and credentials, require authentication to access their data.\nNeedless to say, it is necessary to have an internet connection to import data via API. We will briefly give examples of use of APIs to import data, and link you to further resources.\nNote: recall that data may be posted on a website without an API, which may be easier to retrieve. For example a posted CSV file may be accessible simply by providing the site URL to import() as described in the section on importing from Github.\n\nHTTP request\nThe API exchange is most commonly done through an HTTP request. HTTP is Hypertext Transfer Protocol, and is the underlying format of a request/response between a client and a server. The exact input and output may vary depending on the type of API but the process is the same - a “Request” (often HTTP Request) from the user, often containing a query, followed by a “Response”, containing status information about the request and possibly the requested content.\nHere are a few components of an HTTP request:\n\nThe URL of the API endpoint\nThe “Method” (or “Verb”)\nHeaders\nBody\n\nThe HTTP request “method” is the action your want to perform. The two most common HTTP methods are GET and POST but others could include PUT, DELETE, PATCH, etc. When importing data into R it is most likely that you will use GET.\nAfter your request, your computer will receive a “response” in a format similar to what you sent, including URL, HTTP status (Status 200 is what you want!), file type, size, and the desired content. You will then need to parse this response and turn it into a workable data frame within your R environment.\n\n\nPackages\nThe httr package works well for handling HTTP requests in R. It requires little prior knowledge of Web APIs and can be used by people less familiar with software development terminology. In addition, if the HTTP response is .json, you can use jsonlite to parse the response.\n\n# load packages\npacman::p_load(httr, jsonlite, tidyverse)\n\n\n\nPublicly-available data\nBelow is an example of an HTTP request, borrowed from a tutorial from the Trafford Data Lab. This site has several other resources to learn and API exercises.\nScenario: We want to import a list of fast food outlets in the city of Trafford, UK. The data can be accessed from the API of the Food Standards Agency, which provides food hygiene rating data for the United Kingdom.\nHere are the parameters for our request:\n\nHTTP verb: GET\nAPI endpoint URL: http://api.ratings.food.gov.uk/Establishments\n\nSelected parameters: name, address, longitude, latitude, businessTypeId, ratingKey, localAuthorityId\nHeaders: “x-api-version”\nData format(s): JSON, XML\nDocumentation: http://api.ratings.food.gov.uk/help\n\nThe R code would be as follows:\n\n# prepare the request\npath <- \"http://api.ratings.food.gov.uk/Establishments\"\nrequest <- GET(url = path,\n query = list(\n localAuthorityId = 188,\n BusinessTypeId = 7844,\n pageNumber = 1,\n pageSize = 5000),\n add_headers(\"x-api-version\" = \"2\"))\n\n# check for any server error (\"200\" is good!)\nrequest$status_code\n\n# submit the request, parse the response, and convert to a data frame\nresponse <- content(request, as = \"text\", encoding = \"UTF-8\") %>%\n fromJSON(flatten = TRUE) %>%\n pluck(\"establishments\") %>%\n as_tibble()\n\nYou can now clean and use the response data frame, which contains one row per fast food facility.\n\n\nAuthentication required\nSome APIs require authentication - for you to prove who you are, so you can access restricted data. To import these data, you may need to first use a POST method to provide a username, password, or code. This will return an access token, that can be used for subsequent GET method requests to retrieve the desired data.\nBelow is an example of querying data from Go.Data, which is an outbreak investigation tool. Go.Data uses an API for all interactions between the web front-end and smartphone applications used for data collection. Go.Data is used throughout the world. Because outbreak data are sensitive and you should only be able to access data for your outbreak, authentication is required.\nBelow is some sample R code using httr and jsonlite for connecting to the Go.Data API to import data on contact follow-up from your outbreak.\n\n# set credentials for authorization\nurl <- \"https://godatasampleURL.int/\" # valid Go.Data instance url\nusername <- \"username\" # valid Go.Data username \npassword <- \"password\" # valid Go,Data password \noutbreak_id <- \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxx\" # valid Go.Data outbreak ID\n\n# get access token\nurl_request <- paste0(url,\"api/oauth/token?access_token=123\") # define base URL request\n\n# prepare request\nresponse <- POST(\n url = url_request, \n body = list(\n username = username, # use saved username/password from above to authorize \n password = password), \n encode = \"json\")\n\n# execute request and parse response\ncontent <-\n content(response, as = \"text\") %>%\n fromJSON(flatten = TRUE) %>% # flatten nested JSON\n glimpse()\n\n# Save access token from response\naccess_token <- content$access_token # save access token to allow subsequent API calls below\n\n# import outbreak contacts\n# Use the access token \nresponse_contacts <- GET(\n paste0(url,\"api/outbreaks/\",outbreak_id,\"/contacts\"), # GET request\n add_headers(\n Authorization = paste(\"Bearer\", access_token, sep = \" \")))\n\njson_contacts <- content(response_contacts, as = \"text\") # convert to text JSON\n\ncontacts <- as_tibble(fromJSON(json_contacts, flatten = TRUE)) # flatten JSON to tibble\n\nCAUTION: If you are importing large amounts of data from an API requiring authentication, it may time-out. To avoid this, retrieve access_token again before each API GET request and try using filters or limits in the query. \nTIP: The fromJSON() function in the jsonlite package does not fully un-nest the first time it’s executed, so you will likely still have list items in your resulting tibble. You will need to further un-nest for certain variables; depending on how nested your .json is. To view more info on this, view the documentation for the jsonlite package, such as the flatten() function. \nFor more details, View documentation on LoopBack Explorer, the Contact Tracing page or API tips on Go.Data Github repository\nYou can read more about the httr package here\nThis section was also informed by this tutorial and this tutorial.", "crumbs": [ "Basics", "7  Import and export" @@ -537,8 +537,8 @@ "objectID": "new_pages/importing.html#export", "href": "new_pages/importing.html#export", "title": "7  Import and export", - "section": "7.11 Export", - "text": "7.11 Export\n\nWith rio package\nWith rio, you can use the export() function in a very similar way to import(). First give the name of the R object you want to save (e.g. linelist) and then in quotes put the file path where you want to save the file, including the desired file name and file extension. For example:\nThis saves the data frame linelist as an Excel workbook to the working directory/R project root folder:\n\nexport(linelist, \"my_linelist.xlsx\") # will save to working directory\n\nYou could save the same data frame as a csv file by changing the extension. For example, we also save it to a file path constructed with here():\n\nexport(linelist, here(\"data\", \"clean\", \"my_linelist.csv\"))\n\n\n\nTo clipboard\nTo export a data frame to your computer’s “clipboard” (to then paste into another software like Excel, Google Spreadsheets, etc.) you can use write_clip() from the clipr package.\n\n# export the linelist data frame to your system's clipboard\nclipr::write_clip(linelist)", + "section": "7.10 Export", + "text": "7.10 Export\n\nWith rio package\nWith rio, you can use the export() function in a very similar way to import(). First give the name of the R object you want to save (e.g. linelist) and then in quotes put the file path where you want to save the file, including the desired file name and file extension. For example:\nThis saves the data frame linelist as an Excel workbook to the working directory/R project root folder:\n\nexport(linelist, \"my_linelist.xlsx\") # will save to working directory\n\nYou could save the same data frame as a csv file by changing the extension. For example, we also save it to a file path constructed with here():\n\nexport(linelist, here(\"data\", \"clean\", \"my_linelist.csv\"))\n\n\n\nTo clipboard\nTo export a data frame to your computer’s “clipboard” (to then paste into another software like Excel, Google Spreadsheets, etc.) you can use write_clip() from the clipr package.\n\n# export the linelist data frame to your system's clipboard\nclipr::write_clip(linelist)", "crumbs": [ "Basics", "7  Import and export" @@ -548,8 +548,8 @@ "objectID": "new_pages/importing.html#import_rds", "href": "new_pages/importing.html#import_rds", "title": "7  Import and export", - "section": "7.12 RDS files", - "text": "7.12 RDS files\nAlong with .csv, .xlsx, etc, you can also export/save R data frames as .rds files. This is a file format specific to R, and is very useful if you know you will work with the exported data again in R.\nThe classes of columns are stored, so you don’t have do to cleaning again when it is imported (with an Excel or even a CSV file this can be a headache!). It is also a smaller file, which is useful for export and import if your dataset is large.\nFor example, if you work in an Epidemiology team and need to send files to a GIS team for mapping, and they use R as well, just send them the .rds file! Then all the column classes are retained and they have less work to do.\n\nexport(linelist, here(\"data\", \"clean\", \"my_linelist.rds\"))", + "section": "7.11 RDS files", + "text": "7.11 RDS files\nAlong with .csv, .xlsx, etc, you can also export (save) R data frames as .rds files. This is a file format specific to R, and is very useful if you know you will work with the exported data again in R.\nThe classes of columns are stored, so you don’t have do to cleaning again when it is imported (with an Excel or even a CSV file this can be a headache!). It is also a smaller file, which is useful for export and import if your dataset is large.\nFor example, if you work in an Epidemiology team and need to send files to a GIS team for mapping, and they use R as well, just send them the .rds file! Then all the column classes are retained and they have less work to do.\n\nexport(linelist, here(\"data\", \"clean\", \"my_linelist.rds\"))", "crumbs": [ "Basics", "7  Import and export" @@ -559,8 +559,8 @@ "objectID": "new_pages/importing.html#import_rdata", "href": "new_pages/importing.html#import_rdata", "title": "7  Import and export", - "section": "7.13 Rdata files and lists", - "text": "7.13 Rdata files and lists\n.Rdata files can store multiple R objects - for example multiple data frames, model results, lists, etc. This can be very useful to consolidate or share a lot of your data for a given project.\nIn the below example, multiple R objects are stored within the exported file “my_objects.Rdata”:\n\nrio::export(my_list, my_dataframe, my_vector, \"my_objects.Rdata\")\n\nNote: if you are trying to import a list, use import_list() from rio to import it with the complete original structure and contents.\n\nrio::import_list(\"my_list.Rdata\")", + "section": "7.12 Rdata files and lists", + "text": "7.12 Rdata files and lists\n.Rdata files can store multiple R objects - for example multiple data frames, model results, lists, etc. This can be very useful to consolidate or share a lot of your data for a given project.\nIn the below example, multiple R objects are stored within the exported file “my_objects.Rdata”:\n\nrio::export(my_list, my_dataframe, my_vector, \"my_objects.Rdata\")\n\nNote: if you are trying to import a list, use import_list() from rio to import it with the complete original structure and contents.\n\nrio::import_list(\"my_list.Rdata\")", "crumbs": [ "Basics", "7  Import and export" @@ -570,8 +570,8 @@ "objectID": "new_pages/importing.html#saving-plots", "href": "new_pages/importing.html#saving-plots", "title": "7  Import and export", - "section": "7.14 Saving plots", - "text": "7.14 Saving plots\nInstructions on how to save plots, such as those created by ggplot(), are discussed in depth in the ggplot basics page.\nIn brief, run ggsave(\"my_plot_filepath_and_name.png\") after printing your plot. You can either provide a saved plot object to the plot = argument, or only specify the destination file path (with file extension) to save the most recently-displayed plot. You can also control the width =, height =, units =, and dpi =.\nHow to save a network graph, such as a transmission tree, is addressed in the page on Transmission chains.", + "section": "7.13 Saving plots", + "text": "7.13 Saving plots\nInstructions on how to save plots, such as those created by ggplot(), are discussed in depth in the ggplot basics page.\nIn brief, run ggsave(\"my_plot_filepath_and_name.png\") after printing your plot. You can either provide a saved plot object to the plot = argument, or only specify the destination file path (with file extension) to save the most recently-displayed plot. You can also control the width =, height =, units =, and dpi =.\nHow to save a network graph, such as a transmission tree, is addressed in the page on Transmission chains.", "crumbs": [ "Basics", "7  Import and export" @@ -581,8 +581,8 @@ "objectID": "new_pages/importing.html#resources", "href": "new_pages/importing.html#resources", "title": "7  Import and export", - "section": "7.15 Resources", - "text": "7.15 Resources\nThe R Data Import/Export Manual\nR 4 Data Science chapter on data import\nggsave() documentation\nBelow is a table, taken from the rio online vignette. For each type of data it shows: the expected file extension, the package rio uses to import or export the data, and whether this functionality is included in the default installed version of rio.\n\n\n\n\n\n\n\n\n\n\nFormat\nTypical Extension\nImport Package\nExport Package\nInstalled by Default\n\n\n\n\nComma-separated data\n.csv\ndata.table fread()\ndata.table\nYes\n\n\nPipe-separated data\n.psv\ndata.table fread()\ndata.table\nYes\n\n\nTab-separated data\n.tsv\ndata.table fread()\ndata.table\nYes\n\n\nSAS\n.sas7bdat\nhaven\nhaven\nYes\n\n\nSPSS\n.sav\nhaven\nhaven\nYes\n\n\nStata\n.dta\nhaven\nhaven\nYes\n\n\nSAS\nXPORT\n.xpt\nhaven\nhaven\n\n\nSPSS Portable\n.por\nhaven\n\nYes\n\n\nExcel\n.xls\nreadxl\n\nYes\n\n\nExcel\n.xlsx\nreadxl\nopenxlsx\nYes\n\n\nR syntax\n.R\nbase\nbase\nYes\n\n\nSaved R objects\n.RData, .rda\nbase\nbase\nYes\n\n\nSerialized R objects\n.rds\nbase\nbase\nYes\n\n\nEpiinfo\n.rec\nforeign\n\nYes\n\n\nMinitab\n.mtp\nforeign\n\nYes\n\n\nSystat\n.syd\nforeign\n\nYes\n\n\n“XBASE”\ndatabase files\n.dbf\nforeign\nforeign\n\n\nWeka Attribute-Relation File Format\n.arff\nforeign\nforeign\nYes\n\n\nData Interchange Format\n.dif\nutils\n\nYes\n\n\nFortran data\nno recognized extension\nutils\n\nYes\n\n\nFixed-width format data\n.fwf\nutils\nutils\nYes\n\n\ngzip comma-separated data\n.csv.gz\nutils\nutils\nYes\n\n\nCSVY (CSV + YAML metadata header)\n.csvy\ncsvy\ncsvy\nNo\n\n\nEViews\n.wf1\nhexView\n\nNo\n\n\nFeather R/Python interchange format\n.feather\nfeather\nfeather\nNo\n\n\nFast Storage\n.fst\nfst\nfst\nNo\n\n\nJSON\n.json\njsonlite\njsonlite\nNo\n\n\nMatlab\n.mat\nrmatio\nrmatio\nNo\n\n\nOpenDocument Spreadsheet\n.ods\nreadODS\nreadODS\nNo\n\n\nHTML Tables\n.html\nxml2\nxml2\nNo\n\n\nShallow XML documents\n.xml\nxml2\nxml2\nNo\n\n\nYAML\n.yml\nyaml\nyaml\nNo\n\n\nClipboard default is tsv\n\nclipr\nclipr\nNo", + "section": "7.14 Resources", + "text": "7.14 Resources\nR Data Import/Export Manual\nR 4 Data Science chapter on data import\nggsave() documentation\nBelow is a table, taken from the rio online vignette. For each type of data it shows: the expected file extension, the package rio uses to import or export the data, and whether this functionality is included in the default installed version of rio.\n\n\n\n\n\n\n\n\n\n\nFormat\nTypical Extension\nImport Package\nExport Package\nInstalled by Default\n\n\n\n\nComma-separated data\n.csv\ndata.table fread()\ndata.table\nYes\n\n\nPipe-separated data\n.psv\ndata.table fread()\ndata.table\nYes\n\n\nTab-separated data\n.tsv\ndata.table fread()\ndata.table\nYes\n\n\nSAS\n.sas7bdat\nhaven\nhaven\nYes\n\n\nSPSS\n.sav\nhaven\nhaven\nYes\n\n\nStata\n.dta\nhaven\nhaven\nYes\n\n\nSAS\nXPORT\n.xpt\nhaven\nhaven\n\n\nSPSS Portable\n.por\nhaven\n\nYes\n\n\nExcel\n.xls\nreadxl\n\nYes\n\n\nExcel\n.xlsx\nreadxl\nopenxlsx\nYes\n\n\nR syntax\n.R\nbase\nbase\nYes\n\n\nSaved R objects\n.RData, .rda\nbase\nbase\nYes\n\n\nSerialized R objects\n.rds\nbase\nbase\nYes\n\n\nEpiinfo\n.rec\nforeign\n\nYes\n\n\nMinitab\n.mtp\nforeign\n\nYes\n\n\nSystat\n.syd\nforeign\n\nYes\n\n\n“XBASE”\ndatabase files\n.dbf\nforeign\nforeign\n\n\nWeka Attribute-Relation File Format\n.arff\nforeign\nforeign\nYes\n\n\nData Interchange Format\n.dif\nutils\n\nYes\n\n\nFortran data\nno recognized extension\nutils\n\nYes\n\n\nFixed-width format data\n.fwf\nutils\nutils\nYes\n\n\ngzip comma-separated data\n.csv.gz\nutils\nutils\nYes\n\n\nCSVY (CSV + YAML metadata header)\n.csvy\ncsvy\ncsvy\nNo\n\n\nEViews\n.wf1\nhexView\n\nNo\n\n\nFeather R/Python interchange format\n.feather\nfeather\nfeather\nNo\n\n\nFast Storage\n.fst\nfst\nfst\nNo\n\n\nJSON\n.json\njsonlite\njsonlite\nNo\n\n\nMatlab\n.mat\nrmatio\nrmatio\nNo\n\n\nOpenDocument Spreadsheet\n.ods\nreadODS\nreadODS\nNo\n\n\nHTML Tables\n.html\nxml2\nxml2\nNo\n\n\nShallow XML documents\n.xml\nxml2\nxml2\nNo\n\n\nYAML\n.yml\nyaml\nyaml\nNo\n\n\nClipboard default is tsv\n\nclipr\nclipr\nNo", "crumbs": [ "Basics", "7  Import and export" @@ -604,7 +604,7 @@ "href": "new_pages/cleaning.html#cleaning-pipeline", "title": "8  Cleaning data and core functions", "section": "8.1 Cleaning pipeline", - "text": "8.1 Cleaning pipeline\nThis page proceeds through typical cleaning steps, adding them sequentially to a cleaning pipe chain.\nIn epidemiological analysis and data processing, cleaning steps are often performed sequentially, linked together. In R, this often manifests as a cleaning “pipeline”, where the raw dataset is passed or “piped” from one cleaning step to another.\nSuch chains utilize dplyr “verb” functions and the magrittr pipe operator %>%. This pipe begins with the “raw” data (“linelist_raw.xlsx”) and ends with a “clean” R data frame (linelist) that can be used, saved, exported, etc.\nIn a cleaning pipeline the order of the steps is important. Cleaning steps might include:\n\nImporting of data\n\nColumn names cleaned or changed\n\nDe-duplication\n\nColumn creation and transformation (e.g. re-coding or standardising values)\n\nRows filtered or added", + "text": "8.1 Cleaning pipeline\nThis page proceeds through typical cleaning steps, adding them sequentially to a cleaning pipe chain.\nIn epidemiological analysis and data processing, cleaning steps are often performed sequentially, linked together. In R, this often manifests as a cleaning “pipeline”, where the raw dataset is passed or “piped” from one cleaning step to another.\nSuch chains utilize dplyr “verb” functions and the magrittr pipe operator %>%. This pipe begins with the “raw” data (“linelist_raw.xlsx”) and ends with a “clean” R data frame (linelist) that can be used, saved, exported, etc.\nIn a cleaning pipeline the order of the steps is important. Cleaning steps might include:\n\nImporting of data.\n\nColumn names cleaned or changed.\n\nDe-duplication.\n\nColumn creation and transformation (e.g. re-coding or standardising values).\n\nRows filtered or added.", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -626,7 +626,7 @@ "href": "new_pages/cleaning.html#import-data", "title": "8  Cleaning data and core functions", "section": "8.3 Import data", - "text": "8.3 Import data\n\nImport\nHere we import the “raw” case linelist Excel file using the import() function from the package rio. The rio package flexibly handles many types of files (e.g. .xlsx, .csv, .tsv, .rds. See the page on Import and export for more information and tips on unusual situations (e.g. skipping rows, setting missing values, importing Google sheets, etc).\nIf you want to follow along, click to download the “raw” linelist (as .xlsx file).\nIf your dataset is large and takes a long time to import, it can be useful to have the import command be separate from the pipe chain and the “raw” saved as a distinct file. This also allows easy comparison between the original and cleaned versions.\nBelow we import the raw Excel file and save it as the data frame linelist_raw. We assume the file is located in your working directory or R project root, and so no sub-folders are specified in the file path.\n\nlinelist_raw <- import(\"linelist_raw.xlsx\")\n\nYou can view the first 50 rows of the the data frame below. Note: the base R function head(n) allow you to view just the first n rows in the R console.\n\n\n\n\n\n\n\n\nReview\nYou can use the function skim() from the package skimr to get an overview of the entire dataframe (see page on Descriptive tables for more info). Columns are summarised by class/type such as character, numeric. Note: “POSIXct” is a type of raw date class (see Working with dates.\n\nskimr::skim(linelist_raw)\n\n\n\n\nData summary\n\n\nName\nlinelist_raw\n\n\nNumber of rows\n6611\n\n\nNumber of columns\n28\n\n\n_______________________\n\n\n\nColumn type frequency:\n\n\n\ncharacter\n17\n\n\nnumeric\n8\n\n\nPOSIXct\n3\n\n\n________________________\n\n\n\nGroup variables\nNone\n\n\n\nVariable type: character\n\n\n\n\n\n\n\n\n\n\n\n\n\nskim_variable\nn_missing\ncomplete_rate\nmin\nmax\nempty\nn_unique\nwhitespace\n\n\n\n\ncase_id\n137\n0.98\n6\n6\n0\n5888\n0\n\n\ndate onset\n293\n0.96\n10\n10\n0\n580\n0\n\n\noutcome\n1500\n0.77\n5\n7\n0\n2\n0\n\n\ngender\n324\n0.95\n1\n1\n0\n2\n0\n\n\nhospital\n1512\n0.77\n5\n36\n0\n13\n0\n\n\ninfector\n2323\n0.65\n6\n6\n0\n2697\n0\n\n\nsource\n2323\n0.65\n5\n7\n0\n2\n0\n\n\nage\n107\n0.98\n1\n2\n0\n75\n0\n\n\nage_unit\n7\n1.00\n5\n6\n0\n2\n0\n\n\nfever\n258\n0.96\n2\n3\n0\n2\n0\n\n\nchills\n258\n0.96\n2\n3\n0\n2\n0\n\n\ncough\n258\n0.96\n2\n3\n0\n2\n0\n\n\naches\n258\n0.96\n2\n3\n0\n2\n0\n\n\nvomit\n258\n0.96\n2\n3\n0\n2\n0\n\n\ntime_admission\n844\n0.87\n5\n5\n0\n1091\n0\n\n\nmerged_header\n0\n1.00\n1\n1\n0\n1\n0\n\n\n…28\n0\n1.00\n1\n1\n0\n1\n0\n\n\n\nVariable type: numeric\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nskim_variable\nn_missing\ncomplete_rate\nmean\nsd\np0\np25\np50\np75\np100\n\n\n\n\ngeneration\n7\n1.00\n16.60\n5.71\n0.00\n13.00\n16.00\n20.00\n37.00\n\n\nlon\n7\n1.00\n-13.23\n0.02\n-13.27\n-13.25\n-13.23\n-13.22\n-13.21\n\n\nlat\n7\n1.00\n8.47\n0.01\n8.45\n8.46\n8.47\n8.48\n8.49\n\n\nrow_num\n0\n1.00\n3240.91\n1857.83\n1.00\n1647.50\n3241.00\n4836.50\n6481.00\n\n\nwt_kg\n7\n1.00\n52.69\n18.59\n-11.00\n41.00\n54.00\n66.00\n111.00\n\n\nht_cm\n7\n1.00\n125.25\n49.57\n4.00\n91.00\n130.00\n159.00\n295.00\n\n\nct_blood\n7\n1.00\n21.26\n1.67\n16.00\n20.00\n22.00\n22.00\n26.00\n\n\ntemp\n158\n0.98\n38.60\n0.95\n35.20\n38.30\n38.80\n39.20\n40.80\n\n\n\nVariable type: POSIXct\n\n\n\n\n\n\n\n\n\n\n\n\nskim_variable\nn_missing\ncomplete_rate\nmin\nmax\nmedian\nn_unique\n\n\n\n\ninfection date\n2322\n0.65\n2012-04-09\n2015-04-27\n2014-10-04\n538\n\n\nhosp date\n7\n1.00\n2012-04-20\n2015-04-30\n2014-10-15\n570\n\n\ndate_of_outcome\n1068\n0.84\n2012-05-14\n2015-06-04\n2014-10-26\n575", + "text": "8.3 Import data\n\nImport\nHere we import the “raw” case linelist Excel file using the import() function from the package rio. The rio package flexibly handles many types of files (e.g. .xlsx, .csv, .tsv, .rds. See the page on Import and export for more information and tips on unusual situations (e.g. skipping rows, setting missing values, importing Google sheets, etc).\nIf you want to follow along, click to download the “raw” linelist (as .xlsx file).\nIf your dataset is large and takes a long time to import, it can be useful to have the import command be separate from the pipe chain and the “raw” saved as a distinct file. This also allows easy comparison between the original and cleaned versions.\nBelow we import the raw Excel file and save it as the data frame linelist_raw. We assume the file is located in your working directory or R project root, and so no sub-folders are specified in the file path.\n\nlinelist_raw <- import(\"linelist_raw.xlsx\")\n\nYou can view the first 50 rows of the the data frame below. Note: the base R function head(n) allow you to view just the first n rows in the R console.\n\n\n\n\n\n\n\n\nReview\nYou can use the function skim() from the package skimr to get an overview of the entire dataframe (see page on Descriptive tables for more info). Columns are summarised by class/type such as character, numeric. Note: “POSIXct” is a type of raw date class (see Working with dates).\n\nskimr::skim(linelist_raw)\n\n\n\n\nData summary\n\n\nName\nlinelist_raw\n\n\nNumber of rows\n6611\n\n\nNumber of columns\n28\n\n\n_______________________\n\n\n\nColumn type frequency:\n\n\n\ncharacter\n17\n\n\nnumeric\n8\n\n\nPOSIXct\n3\n\n\n________________________\n\n\n\nGroup variables\nNone\n\n\n\nVariable type: character\n\n\n\n\n\n\n\n\n\n\n\n\n\nskim_variable\nn_missing\ncomplete_rate\nmin\nmax\nempty\nn_unique\nwhitespace\n\n\n\n\ncase_id\n137\n0.98\n6\n6\n0\n5888\n0\n\n\ndate onset\n293\n0.96\n10\n10\n0\n580\n0\n\n\noutcome\n1500\n0.77\n5\n7\n0\n2\n0\n\n\ngender\n324\n0.95\n1\n1\n0\n2\n0\n\n\nhospital\n1512\n0.77\n5\n36\n0\n13\n0\n\n\ninfector\n2323\n0.65\n6\n6\n0\n2697\n0\n\n\nsource\n2323\n0.65\n5\n7\n0\n2\n0\n\n\nage\n107\n0.98\n1\n2\n0\n75\n0\n\n\nage_unit\n7\n1.00\n5\n6\n0\n2\n0\n\n\nfever\n258\n0.96\n2\n3\n0\n2\n0\n\n\nchills\n258\n0.96\n2\n3\n0\n2\n0\n\n\ncough\n258\n0.96\n2\n3\n0\n2\n0\n\n\naches\n258\n0.96\n2\n3\n0\n2\n0\n\n\nvomit\n258\n0.96\n2\n3\n0\n2\n0\n\n\ntime_admission\n844\n0.87\n5\n5\n0\n1091\n0\n\n\nmerged_header\n0\n1.00\n1\n1\n0\n1\n0\n\n\n…28\n0\n1.00\n1\n1\n0\n1\n0\n\n\n\nVariable type: numeric\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nskim_variable\nn_missing\ncomplete_rate\nmean\nsd\np0\np25\np50\np75\np100\n\n\n\n\ngeneration\n7\n1.00\n16.60\n5.71\n0.00\n13.00\n16.00\n20.00\n37.00\n\n\nlon\n7\n1.00\n-13.23\n0.02\n-13.27\n-13.25\n-13.23\n-13.22\n-13.21\n\n\nlat\n7\n1.00\n8.47\n0.01\n8.45\n8.46\n8.47\n8.48\n8.49\n\n\nrow_num\n0\n1.00\n3240.91\n1857.83\n1.00\n1647.50\n3241.00\n4836.50\n6481.00\n\n\nwt_kg\n7\n1.00\n52.69\n18.59\n-11.00\n41.00\n54.00\n66.00\n111.00\n\n\nht_cm\n7\n1.00\n125.25\n49.57\n4.00\n91.00\n130.00\n159.00\n295.00\n\n\nct_blood\n7\n1.00\n21.26\n1.67\n16.00\n20.00\n22.00\n22.00\n26.00\n\n\ntemp\n158\n0.98\n38.60\n0.95\n35.20\n38.30\n38.80\n39.20\n40.80\n\n\n\nVariable type: POSIXct\n\n\n\n\n\n\n\n\n\n\n\n\nskim_variable\nn_missing\ncomplete_rate\nmin\nmax\nmedian\nn_unique\n\n\n\n\ninfection date\n2322\n0.65\n2012-04-09\n2015-04-27\n2014-10-04\n538\n\n\nhosp date\n7\n1.00\n2012-04-20\n2015-04-30\n2014-10-15\n570\n\n\ndate_of_outcome\n1068\n0.84\n2012-05-14\n2015-06-04\n2014-10-26\n575", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -637,7 +637,7 @@ "href": "new_pages/cleaning.html#column-names", "title": "8  Cleaning data and core functions", "section": "8.4 Column names", - "text": "8.4 Column names\nIn R, column names are the “header” or “top” value of a column. They are used to refer to columns in the code, and serve as a default label in figures.\nOther statistical software such as SAS and STATA use “labels” that co-exist as longer printed versions of the shorter column names. While R does offer the possibility of adding column labels to the data, this is not emphasized in most practice. To make column names “printer-friendly” for figures, one typically adjusts their display within the plotting commands that create the outputs (e.g. axis or legend titles of a plot, or column headers in a printed table - see the scales section of the ggplot tips page and Tables for presentation pages). If you want to assign column labels in the data, read more online here and here.\nAs R column names are used very often, so they must have “clean” syntax. We suggest the following:\n\nShort names\nNo spaces (replace with underscores _ )\nNo unusual characters (&, #, <, >, …)\n\nSimilar style nomenclature (e.g. all date columns named like date_onset, date_report, date_death…)\n\nThe columns names of linelist_raw are printed below using names() from base R. We can see that initially:\n\nSome names contain spaces (e.g. infection date)\n\nDifferent naming patterns are used for dates (date onset vs. infection date)\n\nThere must have been a merged header across the two last columns in the .xlsx. We know this because the name of two merged columns (“merged_header”) was assigned by R to the first column, and the second column was assigned a placeholder name “…28” (as it was then empty and is the 28th column).\n\n\nnames(linelist_raw)\n\n [1] \"case_id\" \"generation\" \"infection date\" \"date onset\" \n [5] \"hosp date\" \"date_of_outcome\" \"outcome\" \"gender\" \n [9] \"hospital\" \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \"row_num\" \n[17] \"wt_kg\" \"ht_cm\" \"ct_blood\" \"fever\" \n[21] \"chills\" \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \"...28\" \n\n\nNOTE: To reference a column name that includes spaces, surround the name with back-ticks, for example: linelist$` '\\x60infection date\\x60'`. note that on your keyboard, the back-tick (`) is different from the single quotation mark (’).\n\nAutomatic cleaning\nThe function clean_names() from the package janitor standardizes column names and makes them unique by doing the following:\n\nConverts all names to consist of only underscores, numbers, and letters\n\nAccented characters are transliterated to ASCII (e.g. german o with umlaut becomes “o”, spanish “enye” becomes “n”)\n\nCapitalization preference for the new column names can be specified using the case = argument (“snake” is default, alternatives include “sentence”, “title”, “small_camel”…)\n\nYou can specify specific name replacements by providing a vector to the replace = argument (e.g. replace = c(onset = \"date_of_onset\"))\n\nHere is an online vignette\n\nBelow, the cleaning pipeline begins by using clean_names() on the raw linelist.\n\n# pipe the raw dataset through the function clean_names(), assign result as \"linelist\" \nlinelist <- linelist_raw %>% \n janitor::clean_names()\n\n# see the new column names\nnames(linelist)\n\n [1] \"case_id\" \"generation\" \"infection_date\" \"date_onset\" \n [5] \"hosp_date\" \"date_of_outcome\" \"outcome\" \"gender\" \n [9] \"hospital\" \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \"row_num\" \n[17] \"wt_kg\" \"ht_cm\" \"ct_blood\" \"fever\" \n[21] \"chills\" \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \"x28\" \n\n\nNOTE: The last column name “…28” was changed to “x28”.\n\n\nManual name cleaning\nRe-naming columns manually is often necessary, even after the standardization step above. Below, re-naming is performed using the rename() function from the dplyr package, as part of a pipe chain. rename() uses the style NEW = OLD - the new column name is given before the old column name.\nBelow, a re-naming command is added to the cleaning pipeline. Spaces have been added strategically to align code for easier reading.\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome)\n\nNow you can see that the columns names have been changed:\n\n\n [1] \"case_id\" \"generation\" \"date_infection\" \n [4] \"date_onset\" \"date_hospitalisation\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \"hospital\" \n[10] \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \n[16] \"row_num\" \"wt_kg\" \"ht_cm\" \n[19] \"ct_blood\" \"fever\" \"chills\" \n[22] \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \n[28] \"x28\" \n\n\n\nRename by column position\nYou can also rename by column position, instead of column name, for example:\n\nrename(newNameForFirstColumn = 1,\n newNameForSecondColumn = 2)\n\n\n\nRename via select() and summarise()\nAs a shortcut, you can also rename columns within the dplyr select() and summarise() functions. select() is used to keep only certain columns (and is covered later in this page). summarise() is covered in the Grouping data and Descriptive tables pages. These functions also uses the format new_name = old_name. Here is an example:\n\nlinelist_raw %>% \n select(# NEW name # OLD name\n date_infection = `infection date`, # rename and KEEP ONLY these columns\n date_hospitalisation = `hosp date`)\n\n\n\n\nOther challenges\n\nEmpty Excel column names\nR cannot have dataset columns that do not have column names (headers). So, if you import an Excel dataset with data but no column headers, R will fill-in the headers with names like “…1” or “…2”. The number represents the column number (e.g. if the 4th column in the dataset has no header, then R will name it “…4”).\nYou can clean these names manually by referencing their position number (see example above), or their assigned name (linelist_raw$...1).\n\n\nMerged Excel column names and cells\nMerged cells in an Excel file are a common occurrence when receiving data. As explained in Transition to R, merged cells can be nice for human reading of data, but are not “tidy data” and cause many problems for machine reading of data. R cannot accommodate merged cells.\nRemind people doing data entry that human-readable data is not the same as machine-readable data. Strive to train users about the principles of tidy data. If at all possible, try to change procedures so that data arrive in a tidy format without merged cells.\n\nEach variable must have its own column.\n\nEach observation must have its own row.\n\nEach value must have its own cell.\n\nWhen using rio’s import() function, the value in a merged cell will be assigned to the first cell and subsequent cells will be empty.\nOne solution to deal with merged cells is to import the data with the function readWorkbook() from the package openxlsx. Set the argument fillMergedCells = TRUE. This gives the value in a merged cell to all cells within the merge range.\n\nlinelist_raw <- openxlsx::readWorkbook(\"linelist_raw.xlsx\", fillMergedCells = TRUE)\n\nDANGER: If column names are merged with readWorkbook(), you will end up with duplicate column names, which you will need to fix manually - R does not work well with duplicate column names! You can re-name them by referencing their position (e.g. column 5), as explained in the section on manual column name cleaning.", + "text": "8.4 Column names\nIn R, column names are the “header” or “top” value of a column. They are used to refer to columns in the code, and serve as a default label in figures.\nOther statistical software such as SAS and STATA use “labels” that co-exist as longer printed versions of the shorter column names. While R does offer the possibility of adding column labels to the data, this is not emphasized in most practice. To make column names “printer-friendly” for figures, one typically adjusts their display within the plotting commands that create the outputs (e.g. axis or legend titles of a plot, or column headers in a printed table - see the scales section of the ggplot tips page and Tables for presentation pages). If you want to assign column labels in the data, read more online here and here.\nAs R column names are used very often, so they must have “clean” syntax. We suggest the following:\n\nShort names.\nNo spaces (replace with underscores _ ).\nNo unusual characters (&, #, <, >, …).\n\nSimilar style nomenclature (e.g. all date columns named like date_onset, date_report, date_death…).\n\nThe columns names of linelist_raw are printed below using names() from base R. We can see that initially:\n\nSome names contain spaces (e.g. infection date).\n\nDifferent naming patterns are used for dates (date onset vs. infection date).\n\nThere must have been a merged header across the two last columns in the .xlsx. We know this because the name of two merged columns (“merged_header”) was assigned by R to the first column, and the second column was assigned a placeholder name “…28” (as it was then empty and is the 28th column).\n\n\nnames(linelist_raw)\n\n [1] \"case_id\" \"generation\" \"infection date\" \"date onset\" \n [5] \"hosp date\" \"date_of_outcome\" \"outcome\" \"gender\" \n [9] \"hospital\" \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \"row_num\" \n[17] \"wt_kg\" \"ht_cm\" \"ct_blood\" \"fever\" \n[21] \"chills\" \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \"...28\" \n\n\nNOTE: To reference a column name that includes spaces, surround the name with back-ticks, for example: linelist$`infection date`. note that on your keyboard, the back-tick (`) is different from the single quotation mark (’).\n\nAutomatic cleaning\nThe function clean_names() from the package janitor standardizes column names and makes them unique by doing the following:\n\nConverts all names to consist of only underscores, numbers, and letters.\n\nAccented characters are transliterated to ASCII (e.g. german o with umlaut becomes “o”, spanish “enye” becomes “n”).\n\nCapitalization preference for the new column names can be specified using the case = argument (“snake” is default, alternatives include “sentence”, “title”, “small_camel”…).\n\nYou can specify specific name replacements by providing a vector to the replace = argument (e.g. replace = c(onset = \"date_of_onset\")).\n\nHere is an online vignette.\n\nBelow, the cleaning pipeline begins by using clean_names() on the raw linelist.\n\n# pipe the raw dataset through the function clean_names(), assign result as \"linelist\" \nlinelist <- linelist_raw %>% \n janitor::clean_names()\n\n# see the new column names\nnames(linelist)\n\n [1] \"case_id\" \"generation\" \"infection_date\" \"date_onset\" \n [5] \"hosp_date\" \"date_of_outcome\" \"outcome\" \"gender\" \n [9] \"hospital\" \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \"row_num\" \n[17] \"wt_kg\" \"ht_cm\" \"ct_blood\" \"fever\" \n[21] \"chills\" \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \"x28\" \n\n\nNOTE: The last column name “…28” was changed to “x28”.\n\n\nManual name cleaning\nRe-naming columns manually is often necessary, even after the standardization step above. Below, re-naming is performed using the rename() function from the dplyr package, as part of a pipe chain. rename() uses the style NEW = OLD, the new column name is given before the old column name.\nBelow, a re-naming command is added to the cleaning pipeline. Spaces have been added strategically to align code for easier reading.\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome)\n\nNow you can see that the columns names have been changed:\n\n\n [1] \"case_id\" \"generation\" \"date_infection\" \n [4] \"date_onset\" \"date_hospitalisation\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \"hospital\" \n[10] \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \n[16] \"row_num\" \"wt_kg\" \"ht_cm\" \n[19] \"ct_blood\" \"fever\" \"chills\" \n[22] \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \n[28] \"x28\" \n\n\n\nRename by column position\nYou can also rename by column position, instead of column name, for example:\n\nrename(newNameForFirstColumn = 1,\n newNameForSecondColumn = 2)\n\n\n\nRename via select() and summarise()\nAs a shortcut, you can also rename columns within the dplyr select() and summarise() functions. select() is used to keep only certain columns (and is covered later in this page). summarise() is covered in the Grouping data and Descriptive tables pages. These functions also uses the format new_name = old_name. Here is an example:\n\nlinelist_raw %>% \n # rename and KEEP ONLY these columns\n select(# NEW name # OLD name\n date_infection = `infection date`, \n date_hospitalisation = `hosp date`)\n\n\n\n\nOther challenges\n\nEmpty Excel column names\nR cannot have dataset columns that do not have column names (headers). So, if you import an Excel dataset with data but no column headers, R will fill-in the headers with names like “…1” or “…2”. The number represents the column number (e.g. if the 4th column in the dataset has no header, then R will name it “…4”).\nYou can clean these names manually by referencing their position number (see example above), or their assigned name (linelist_raw$...1).\n\n\nMerged Excel column names and cells\nMerged cells in an Excel file are a common occurrence when receiving data. As explained in Transition to R, merged cells can be nice for human reading of data, but are not “tidy data” and cause many problems for machine reading of data. R cannot accommodate merged cells.\nRemind people doing data entry that human-readable data is not the same as machine-readable data. Strive to train users about the principles of tidy data. If at all possible, try to change procedures so that data arrive in a tidy format without merged cells.\n\nEach variable must have its own column.\n\nEach observation must have its own row.\n\nEach value must have its own cell.\n\nWhen using rio’s import() function, the value in a merged cell will be assigned to the first cell and subsequent cells will be empty.\nOne solution to deal with merged cells is to import the data with the function readWorkbook() from the package openxlsx. Set the argument fillMergedCells = TRUE. This gives the value in a merged cell to all cells within the merge range.\n\nlinelist_raw <- openxlsx::readWorkbook(\"linelist_raw.xlsx\", fillMergedCells = TRUE)\n\nDANGER: If column names are merged with readWorkbook(), you will end up with duplicate column names, which you will need to fix manually - R does not work well with duplicate column names! You can re-name them by referencing their position (e.g. column 5), as explained in the section on manual column name cleaning.", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -648,7 +648,7 @@ "href": "new_pages/cleaning.html#select-or-re-order-columns", "title": "8  Cleaning data and core functions", "section": "8.5 Select or re-order columns", - "text": "8.5 Select or re-order columns\nUse select() from dplyr to select the columns you want to retain, and to specify their order in the data frame.\nCAUTION: In the examples below, the linelist data frame is modified with select() and displayed, but not saved. This is for demonstration purposes. The modified column names are printed by piping the data frame to names().\nHere are ALL the column names in the linelist at this point in the cleaning pipe chain:\n\nnames(linelist)\n\n [1] \"case_id\" \"generation\" \"date_infection\" \n [4] \"date_onset\" \"date_hospitalisation\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \"hospital\" \n[10] \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \n[16] \"row_num\" \"wt_kg\" \"ht_cm\" \n[19] \"ct_blood\" \"fever\" \"chills\" \n[22] \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \n[28] \"x28\" \n\n\n\nKeep columns\nSelect only the columns you want to remain\nPut their names in the select() command, with no quotation marks. They will appear in the data frame in the order you provide. Note that if you include a column that does not exist, R will return an error (see use of any_of() below if you want no error in this situation).\n\n# linelist dataset is piped through select() command, and names() prints just the column names\nlinelist %>% \n select(case_id, date_onset, date_hospitalisation, fever) %>% \n names() # display the column names\n\n[1] \"case_id\" \"date_onset\" \"date_hospitalisation\"\n[4] \"fever\" \n\n\n\n\n“tidyselect” helper functions\nThese helper functions exist to make it easy to specify columns to keep, discard, or transform. They are from the package tidyselect, which is included in tidyverse and underlies how columns are selected in dplyr functions.\nFor example, if you want to re-order the columns, everything() is a useful function to signify “all other columns not yet mentioned”. The command below moves columns date_onset and date_hospitalisation to the beginning (left) of the dataset, but keeps all the other columns afterward. Note that everything() is written with empty parentheses:\n\n# move date_onset and date_hospitalisation to beginning\nlinelist %>% \n select(date_onset, date_hospitalisation, everything()) %>% \n names()\n\n [1] \"date_onset\" \"date_hospitalisation\" \"case_id\" \n [4] \"generation\" \"date_infection\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \"hospital\" \n[10] \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \n[16] \"row_num\" \"wt_kg\" \"ht_cm\" \n[19] \"ct_blood\" \"fever\" \"chills\" \n[22] \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \n[28] \"x28\" \n\n\nHere are other “tidyselect” helper functions that also work within dplyr functions like select(), across(), and summarise():\n\neverything() - all other columns not mentioned\n\nlast_col() - the last column\n\nwhere() - applies a function to all columns and selects those which are TRUE\n\ncontains() - columns containing a character string\n\nexample: select(contains(\"time\"))\n\n\nstarts_with() - matches to a specified prefix\n\nexample: select(starts_with(\"date_\"))\n\n\nends_with() - matches to a specified suffix\n\nexample: select(ends_with(\"_post\"))\n\n\nmatches() - to apply a regular expression (regex)\n\nexample: select(matches(\"[pt]al\"))\n\n\nnum_range() - a numerical range like x01, x02, x03\n\nany_of() - matches IF column exists but returns no error if it is not found\n\nexample: select(any_of(date_onset, date_death, cardiac_arrest))\n\n\nIn addition, use normal operators such as c() to list several columns, : for consecutive columns, ! for opposite, & for AND, and | for OR.\nUse where() to specify logical criteria for columns. If providing a function inside where(), do not include the function’s empty parentheses. The command below selects columns that are class Numeric.\n\n# select columns that are class Numeric\nlinelist %>% \n select(where(is.numeric)) %>% \n names()\n\n[1] \"generation\" \"lon\" \"lat\" \"row_num\" \"wt_kg\" \n[6] \"ht_cm\" \"ct_blood\" \"temp\" \n\n\nUse contains() to select only columns in which the column name contains a specified character string. ends_with() and starts_with() provide more nuance.\n\n# select columns containing certain characters\nlinelist %>% \n select(contains(\"date\")) %>% \n names()\n\n[1] \"date_infection\" \"date_onset\" \"date_hospitalisation\"\n[4] \"date_outcome\" \n\n\nThe function matches() works similarly to contains() but can be provided a regular expression (see page on Characters and strings), such as multiple strings separated by OR bars within the parentheses:\n\n# searched for multiple character matches\nlinelist %>% \n select(matches(\"onset|hosp|fev\")) %>% # note the OR symbol \"|\"\n names()\n\n[1] \"date_onset\" \"date_hospitalisation\" \"hospital\" \n[4] \"fever\" \n\n\nCAUTION: If a column name that you specifically provide does not exist in the data, it can return an error and stop your code. Consider using any_of() to cite columns that may or may not exist, especially useful in negative (remove) selections.\nOnly one of these columns exists, but no error is produced and the code continues without stopping your cleaning chain.\n\nlinelist %>% \n select(any_of(c(\"date_onset\", \"village_origin\", \"village_detection\", \"village_residence\", \"village_travel\"))) %>% \n names()\n\n[1] \"date_onset\"\n\n\n\n\nRemove columns\nIndicate which columns to remove by placing a minus symbol “-” in front of the column name (e.g. select(-outcome)), or a vector of column names (as below). All other columns will be retained.\n\nlinelist %>% \n select(-c(date_onset, fever:vomit)) %>% # remove date_onset and all columns from fever to vomit\n names()\n\n [1] \"case_id\" \"generation\" \"date_infection\" \n [4] \"date_hospitalisation\" \"date_outcome\" \"outcome\" \n [7] \"gender\" \"hospital\" \"lon\" \n[10] \"lat\" \"infector\" \"source\" \n[13] \"age\" \"age_unit\" \"row_num\" \n[16] \"wt_kg\" \"ht_cm\" \"ct_blood\" \n[19] \"temp\" \"time_admission\" \"merged_header\" \n[22] \"x28\" \n\n\nYou can also remove a column using base R syntax, by defining it as NULL. For example:\n\nlinelist$date_onset <- NULL # deletes column with base R syntax \n\n\n\nStandalone\nselect() can also be used as an independent command (not in a pipe chain). In this case, the first argument is the original dataframe to be operated upon.\n\n# Create a new linelist with id and age-related columns\nlinelist_age <- select(linelist, case_id, contains(\"age\"))\n\n# display the column names\nnames(linelist_age)\n\n[1] \"case_id\" \"age\" \"age_unit\"\n\n\n\nAdd to the pipe chain\nIn the linelist_raw, there are a few columns we do not need: row_num, merged_header, and x28. We remove them with a select() command in the cleaning pipe chain:\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n #####################################################\n\n # remove column\n select(-c(row_num, merged_header, x28))", + "text": "8.5 Select or re-order columns\nUse select() from dplyr to select the columns you want to retain, and to specify their order in the data frame.\nCAUTION: In the examples below, the linelist data frame is modified with select() and displayed, but not saved. This is for demonstration purposes. The modified column names are printed by piping the data frame to names().\nHere are ALL the column names in the linelist at this point in the cleaning pipe chain:\n\nnames(linelist)\n\n [1] \"case_id\" \"generation\" \"date_infection\" \n [4] \"date_onset\" \"date_hospitalisation\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \"hospital\" \n[10] \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \n[16] \"row_num\" \"wt_kg\" \"ht_cm\" \n[19] \"ct_blood\" \"fever\" \"chills\" \n[22] \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \n[28] \"x28\" \n\n\n\nKeep columns\nSelect only the columns you want to remain\nPut their names in the select() command, with no quotation marks. They will appear in the data frame in the order you provide. Note that if you include a column that does not exist, R will return an error (see use of any_of() below if you want no error in this situation).\n\n# linelist dataset is piped through select() command, and names() prints just the column names\nlinelist %>% \n select(case_id, date_onset, date_hospitalisation, fever) %>% \n names() # display the column names\n\n[1] \"case_id\" \"date_onset\" \"date_hospitalisation\"\n[4] \"fever\" \n\n\n\n\n“tidyselect” helper functions\nThese helper functions exist to make it easy to specify columns to keep, discard, or transform. They are from the package tidyselect, which is included in tidyverse and underlies how columns are selected in dplyr functions.\nFor example, if you want to re-order the columns, everything() is a useful function to signify “all other columns not yet mentioned”. The command below moves columns date_onset and date_hospitalisation to the beginning (left) of the dataset, but keeps all the other columns afterward. Note that everything() is written with empty parentheses:\n\n# move date_onset and date_hospitalisation to beginning\nlinelist %>% \n select(date_onset, date_hospitalisation, everything()) %>% \n names()\n\n [1] \"date_onset\" \"date_hospitalisation\" \"case_id\" \n [4] \"generation\" \"date_infection\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \"hospital\" \n[10] \"lon\" \"lat\" \"infector\" \n[13] \"source\" \"age\" \"age_unit\" \n[16] \"row_num\" \"wt_kg\" \"ht_cm\" \n[19] \"ct_blood\" \"fever\" \"chills\" \n[22] \"cough\" \"aches\" \"vomit\" \n[25] \"temp\" \"time_admission\" \"merged_header\" \n[28] \"x28\" \n\n\nHere are other “tidyselect” helper functions that also work within dplyr functions like select(), across(), and summarise():\n\neverything() - all other columns not mentioned.\n\nlast_col() - the last column.\nwhere() - applies a function to all columns and selects those which are TRUE.\n\ncontains() - columns containing a character string.\n\nexample: select(contains(\"time\")).\n\n\nstarts_with() - matches to a specified prefix.\n\nexample: select(starts_with(\"date_\")).\n\n\nends_with() - matches to a specified suffix.\n\nexample: select(ends_with(\"_post\")).\n\n\nmatches() - to apply a regular expression (regex).\n\nexample: select(matches(\"[pt]al\")).\n\nnum_range() - a numerical range like x01, x02, x03.\n\nany_of() - matches IF column exists but returns no error if it is not found.\n\nexample: select(any_of(date_onset, date_death, cardiac_arrest)).\n\n\nIn addition, use normal operators such as c() to list several columns, : for consecutive columns, ! for opposite, & for AND, and | for OR.\nUse where() to specify logical criteria for columns. If providing a function inside where(), do not include the function’s empty parentheses. The command below selects columns that are class Numeric.\n\n# select columns that are class Numeric\nlinelist %>% \n select(where(is.numeric)) %>% \n names()\n\n[1] \"generation\" \"lon\" \"lat\" \"row_num\" \"wt_kg\" \n[6] \"ht_cm\" \"ct_blood\" \"temp\" \n\n\nUse contains() to select only columns in which the column name contains a specified character string. ends_with() and starts_with() provide more nuance.\n\n# select columns containing certain characters\nlinelist %>% \n select(contains(\"date\")) %>% \n names()\n\n[1] \"date_infection\" \"date_onset\" \"date_hospitalisation\"\n[4] \"date_outcome\" \n\n\nThe function matches() works similarly to contains() but can be provided a regular expression (see page on Characters and strings), such as multiple strings separated by OR bars within the parentheses:\n\n# searched for multiple character matches\nlinelist %>% \n select(matches(\"onset|hosp|fev\")) %>% # note the OR symbol \"|\"\n names()\n\n[1] \"date_onset\" \"date_hospitalisation\" \"hospital\" \n[4] \"fever\" \n\n\nCAUTION: If a column name that you specifically provide does not exist in the data, it can return an error and stop your code. Consider using any_of() to cite columns that may or may not exist, especially useful in negative (remove) selections.\nOnly one of these columns exists, but no error is produced and the code continues without stopping your cleaning chain.\n\nlinelist %>% \n select(any_of(c(\"date_onset\", \"village_origin\", \"village_detection\", \"village_residence\", \"village_travel\"))) %>% \n names()\n\n[1] \"date_onset\"\n\n\n\n\nRemove columns\nIndicate which columns to remove by placing a minus symbol “-” in front of the column name (e.g. select(-outcome)), or a vector of column names (as below). All other columns will be retained.\n\nlinelist %>% \n select(-c(date_onset, fever:vomit)) %>% # remove date_onset and all columns from fever to vomit\n names()\n\n [1] \"case_id\" \"generation\" \"date_infection\" \n [4] \"date_hospitalisation\" \"date_outcome\" \"outcome\" \n [7] \"gender\" \"hospital\" \"lon\" \n[10] \"lat\" \"infector\" \"source\" \n[13] \"age\" \"age_unit\" \"row_num\" \n[16] \"wt_kg\" \"ht_cm\" \"ct_blood\" \n[19] \"temp\" \"time_admission\" \"merged_header\" \n[22] \"x28\" \n\n\nYou can also remove a column using base R syntax, by defining it as NULL. For example:\n\nlinelist$date_onset <- NULL # deletes column with base R syntax \n\n\n\nStandalone\nselect() can also be used as an independent command (not in a pipe chain). In this case, the first argument is the original dataframe to be operated upon.\n\n# Create a new linelist with id and age-related columns\nlinelist_age <- select(linelist, case_id, contains(\"age\"))\n\n# display the column names\nnames(linelist_age)\n\n[1] \"case_id\" \"age\" \"age_unit\"\n\n\n\nAdd to the pipe chain\nIn the linelist_raw, there are a few columns we do not need: row_num, merged_header, and x28. We remove them with a select() command in the cleaning pipe chain:\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n #####################################################\n\n # remove column\n select(-c(row_num, merged_header, x28))", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -670,7 +670,7 @@ "href": "new_pages/cleaning.html#column-creation-and-transformation", "title": "8  Cleaning data and core functions", "section": "8.7 Column creation and transformation", - "text": "8.7 Column creation and transformation\nWe recommend using the dplyr function mutate() to add a new column, or to modify an existing one.\nBelow is an example of creating a new column with mutate(). The syntax is: mutate(new_column_name = value or transformation)\nIn Stata, this is similar to the command generate, but R’s mutate() can also be used to modify an existing column.\n\nNew columns\nThe most basic mutate() command to create a new column might look like this. It creates a new column new_col where the value in every row is 10.\n\nlinelist <- linelist %>% \n mutate(new_col = 10)\n\nYou can also reference values in other columns, to perform calculations. Below, a new column bmi is created to hold the Body Mass Index (BMI) for each case - as calculated using the formula BMI = kg/m^2, using column ht_cm and column wt_kg.\n\nlinelist <- linelist %>% \n mutate(bmi = wt_kg / (ht_cm/100)^2)\n\nIf creating multiple new columns, separate each with a comma and new line. Below are examples of new columns, including ones that consist of values from other columns combined using str_glue() from the stringr package (see page on Characters and strings.\n\nnew_col_demo <- linelist %>% \n mutate(\n new_var_dup = case_id, # new column = duplicate/copy another existing column\n new_var_static = 7, # new column = all values the same\n new_var_static = new_var_static + 5, # you can overwrite a column, and it can be a calculation using other variables\n new_var_paste = stringr::str_glue(\"{hospital} on ({date_hospitalisation})\") # new column = pasting together values from other columns\n ) %>% \n select(case_id, hospital, date_hospitalisation, contains(\"new\")) # show only new columns, for demonstration purposes\n\nReview the new columns. For demonstration purposes, only the new columns and the columns used to create them are shown:\n\n\n\n\n\n\nTIP: A variation on mutate() is the function transmute(). This function adds a new column just like mutate(), but also drops/removes all other columns that you do not mention within its parentheses.\n\n# HIDDEN FROM READER\n# removes new demo columns created above\n# linelist <- linelist %>% \n# select(-contains(\"new_var\"))\n\n\n\nConvert column class\nColumns containing values that are dates, numbers, or logical values (TRUE/FALSE) will only behave as expected if they are correctly classified. There is a difference between “2” of class character and 2 of class numeric!\nThere are ways to set column class during the import commands, but this is often cumbersome. See the R Basics section on object classes to learn more about converting the class of objects and columns.\nFirst, let’s run some checks on important columns to see if they are the correct class. We also saw this in the beginning when we ran skim().\nCurrently, the class of the age column is character. To perform quantitative analyses, we need these numbers to be recognized as numeric!\n\nclass(linelist$age)\n\n[1] \"character\"\n\n\nThe class of the date_onset column is also character! To perform analyses, these dates must be recognized as dates!\n\nclass(linelist$date_onset)\n\n[1] \"character\"\n\n\nTo resolve this, use the ability of mutate() to re-define a column with a transformation. We define the column as itself, but converted to a different class. Here is a basic example, converting or ensuring that the column age is class Numeric:\n\nlinelist <- linelist %>% \n mutate(age = as.numeric(age))\n\nIn a similar way, you can use as.character() and as.logical(). To convert to class Factor, you can use factor() from base R or as_factor() from forcats. Read more about this in the Factors page.\nYou must be careful when converting to class Date. Several methods are explained on the page Working with dates. Typically, the raw date values must all be in the same format for conversion to work correctly (e.g “MM/DD/YYYY”, or “DD MM YYYY”). After converting to class Date, check your data to confirm that each value was converted correctly.\n\n\nGrouped data\nIf your data frame is already grouped (see page on Grouping data), mutate() may behave differently than if the data frame is not grouped. Any summarizing functions, like mean(), median(), max(), etc. will calculate by group, not by all the rows.\n\n# age normalized to mean of ALL rows\nlinelist %>% \n mutate(age_norm = age / mean(age, na.rm=T))\n\n# age normalized to mean of hospital group\nlinelist %>% \n group_by(hospital) %>% \n mutate(age_norm = age / mean(age, na.rm=T))\n\nRead more about using mutate () on grouped dataframes in this tidyverse mutate documentation.\n\n\nTransform multiple columns\nOften to write concise code you want to apply the same transformation to multiple columns at once. A transformation can be applied to multiple columns at once using the across() function from the package dplyr (also contained within tidyverse package). across() can be used with any dplyr function, but is commonly used within select(), mutate(), filter(), or summarise(). See how it is applied to summarise() in the page on Descriptive tables.\nSpecify the columns to the argument .cols = and the function(s) to apply to .fns =. Any additional arguments to provide to the .fns function can be included after a comma, still within across().\n\nacross() column selection\nSpecify the columns to the argument .cols =. You can name them individually, or use “tidyselect” helper functions. Specify the function to .fns =. Note that using the function mode demonstrated below, the function is written without its parentheses ( ).\nHere the transformation as.character() is applied to specific columns named within across().\n\nlinelist <- linelist %>% \n mutate(across(.cols = c(temp, ht_cm, wt_kg), .fns = as.character))\n\nThe “tidyselect” helper functions are available to assist you in specifying columns. They are detailed above in the section on Selecting and re-ordering columns, and they include: everything(), last_col(), where(), starts_with(), ends_with(), contains(), matches(), num_range() and any_of().\nHere is an example of how one would change all columns to character class:\n\n#to change all columns to character class\nlinelist <- linelist %>% \n mutate(across(.cols = everything(), .fns = as.character))\n\nConvert to character all columns where the name contains the string “date” (note the placement of commas and parentheses):\n\n#to change all columns to character class\nlinelist <- linelist %>% \n mutate(across(.cols = contains(\"date\"), .fns = as.character))\n\nBelow, an example of mutating the columns that are currently class POSIXct (a raw datetime class that shows timestamps) - in other words, where the function is.POSIXct() evaluates to TRUE. Then we want to apply the function as.Date() to these columns to convert them to a normal class Date.\n\nlinelist <- linelist %>% \n mutate(across(.cols = where(is.POSIXct), .fns = as.Date))\n\n\nNote that within across() we also use the function where() as is.POSIXct is evaluating to either TRUE or FALSE.\n\nNote that is.POSIXct() is from the package lubridate. Other similar “is” functions like is.character(), is.numeric(), and is.logical() are from base R\n\n\n\nacross() functions\nYou can read the documentation with ?across for details on how to provide functions to across(). A few summary points: there are several ways to specify the function(s) to perform on a column and you can even define your own functions:\n\nYou can provide the function name alone (e.g. mean or as.character)\n\nYou can provide the function in purrr-style (e.g. ~ mean(.x, na.rm = TRUE)) (see this page)\n\nYou can specify multiple functions by providing a list (e.g. list(mean = mean, n_miss = ~ sum(is.na(.x))).\n\nIf you provide multiple functions, multiple transformed columns will be returned per input column, with unique names in the format col_fn. You can adjust how the new columns are named with the .names = argument using glue syntax (see page on Characters and strings) where {.col} and {.fn} are shorthand for the input column and function.\n\n\nHere are a few online resources on using across(): creator Hadley Wickham’s thoughts/rationale\n\n\n\ncoalesce()\nThis dplyr function finds the first non-missing value at each position. It “fills-in” missing values with the first available value in an order you specify.\nHere is an example outside the context of a data frame: Let us say you have two vectors, one containing the patient’s village of detection and another containing the patient’s village of residence. You can use coalesce to pick the first non-missing value for each index:\n\nvillage_detection <- c(\"a\", \"b\", NA, NA)\nvillage_residence <- c(\"a\", \"c\", \"a\", \"d\")\n\nvillage <- coalesce(village_detection, village_residence)\nvillage # print\n\n[1] \"a\" \"b\" \"a\" \"d\"\n\n\nThis works the same if you provide data frame columns: for each row, the function will assign the new column value with the first non-missing value in the columns you provided (in order provided).\n\nlinelist <- linelist %>% \n mutate(village = coalesce(village_detection, village_residence))\n\nThis is an example of a “row-wise” operation. For more complicated row-wise calculations, see the section below on Row-wise calculations.\n\n\nCumulative math\nIf you want a column to reflect the cumulative sum/mean/min/max etc as assessed down the rows of a dataframe to that point, use the following functions:\ncumsum() returns the cumulative sum, as shown below:\n\nsum(c(2,4,15,10)) # returns only one number\n\n[1] 31\n\ncumsum(c(2,4,15,10)) # returns the cumulative sum at each step\n\n[1] 2 6 21 31\n\n\nThis can be used in a dataframe when making a new column. For example, to calculate the cumulative number of cases per day in an outbreak, consider code like this:\n\ncumulative_case_counts <- linelist %>% # begin with case linelist\n count(date_onset) %>% # count of rows per day, as column 'n' \n mutate(cumulative_cases = cumsum(n)) # new column, of the cumulative sum at each row\n\nBelow are the first 10 rows:\n\nhead(cumulative_case_counts, 10)\n\n date_onset n cumulative_cases\n1 2012-04-15 1 1\n2 2012-05-05 1 2\n3 2012-05-08 1 3\n4 2012-05-31 1 4\n5 2012-06-02 1 5\n6 2012-06-07 1 6\n7 2012-06-14 1 7\n8 2012-06-21 1 8\n9 2012-06-24 1 9\n10 2012-06-25 1 10\n\n\nSee the page on Epidemic curves for how to plot cumulative incidence with the epicurve.\nSee also:\ncumsum(), cummean(), cummin(), cummax(), cumany(), cumall()\n\n\nUsing base R\nTo define a new column (or re-define a column) using base R, write the name of data frame, connected with $, to the new column (or the column to be modified). Use the assignment operator <- to define the new value(s). Remember that when using base R you must specify the data frame name before the column name every time (e.g. dataframe$column). Here is an example of creating the bmi column using base R:\n\nlinelist$bmi = linelist$wt_kg / (linelist$ht_cm / 100) ^ 2)\n\n\n\nAdd to pipe chain\nBelow, a new column is added to the pipe chain and some classes are converted.\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ###################################################\n # add new column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n \n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age))", + "text": "8.7 Column creation and transformation\nWe recommend using the dplyr function mutate() to add a new column, or to modify an existing one.\nBelow is an example of creating a new column with mutate(). The syntax is: mutate(new_column_name = value or transformation).\nIn Stata, this is similar to the command generate, but R’s mutate() can also be used to modify an existing column.\n\nNew columns\nThe most basic mutate() command to create a new column might look like this. It creates a new column new_col where the value in every row is 10.\n\nlinelist <- linelist %>% \n mutate(new_col = 10)\n\nYou can also reference values in other columns, to perform calculations. Below, a new column bmi is created to hold the Body Mass Index (BMI) for each case - as calculated using the formula BMI = kg/m^2, using column ht_cm and column wt_kg.\n\nlinelist <- linelist %>% \n mutate(bmi = wt_kg / (ht_cm/100)^2)\n\nIf creating multiple new columns, separate each with a comma and new line. Below are examples of new columns, including ones that consist of values from other columns combined using str_glue() from the stringr package (see page on Characters and strings.\n\nnew_col_demo <- linelist %>% \n mutate(\n new_var_dup = case_id, # new column = duplicate/copy another existing column\n new_var_static = 7, # new column = all values the same\n new_var_static = new_var_static + 5, # you can overwrite a column, and it can be a calculation using other variables\n new_var_paste = stringr::str_glue(\"{hospital} on ({date_hospitalisation})\") # new column = pasting together values from other columns\n ) %>% \n select(case_id, hospital, date_hospitalisation, contains(\"new\")) # show only new columns, for demonstration purposes\n\nReview the new columns. For demonstration purposes, only the new columns and the columns used to create them are shown:\n\n\n\n\n\n\nTIP: A variation on mutate() is the function transmute(). This function adds a new column just like mutate(), but also drops/removes all other columns that you do not mention within its parentheses.\n\n# HIDDEN FROM READER\n# removes new demo columns created above\n# linelist <- linelist %>% \n# select(-contains(\"new_var\"))\n\n\n\nConvert column class\nColumns containing values that are dates, numbers, or logical values (TRUE/FALSE) will only behave as expected if they are correctly classified. There is a difference between “2” of class character and 2 of class numeric!\nThere are ways to set column class during the import commands, but this is often cumbersome. See the R Basics section on object classes to learn more about converting the class of objects and columns.\nFirst, let’s run some checks on important columns to see if they are the correct class. We also saw this in the beginning when we ran skim().\nCurrently, the class of the age column is character. To perform quantitative analyses, we need these numbers to be recognized as numeric!\n\nclass(linelist$age)\n\n[1] \"character\"\n\n\nThe class of the date_onset column is also character! To perform analyses, these dates must be recognized as dates!\n\nclass(linelist$date_onset)\n\n[1] \"character\"\n\n\nTo resolve this, use the ability of mutate() to re-define a column with a transformation. We define the column as itself, but converted to a different class. Here is a basic example, converting or ensuring that the column age is class Numeric:\n\nlinelist <- linelist %>% \n mutate(age = as.numeric(age))\n\nIn a similar way, you can use as.character() and as.logical(). To convert to class Factor, you can use factor() from base R or as_factor() from forcats. Read more about this in the Factors page.\nYou must be careful when converting to class Date. Several methods are explained on the page Working with dates. Typically, the raw date values must all be in the same format for conversion to work correctly (e.g “MM/DD/YYYY”, or “DD MM YYYY”). After converting to class Date, check your data to confirm that each value was converted correctly.\n\n\nGrouped data\nIf your data frame is already grouped (see page on Grouping data), mutate() may behave differently than if the data frame is not grouped. Any summarizing functions, like mean(), median(), max(), etc. will calculate by group, not by all the rows.\n\n# age normalized to mean of ALL rows\nlinelist %>% \n mutate(age_norm = age / mean(age, na.rm=T))\n\n# age normalized to mean of hospital group\nlinelist %>% \n group_by(hospital) %>% \n mutate(age_norm = age / mean(age, na.rm=T))\n\nRead more about using mutate () on grouped dataframes in this tidyverse mutate documentation.\n\n\nTransform multiple columns\nOften to write concise code you want to apply the same transformation to multiple columns at once. A transformation can be applied to multiple columns at once using the across() function from the package dplyr (also contained within tidyverse package). across() can be used with any dplyr function, but is commonly used within select(), mutate(), filter(), or summarise(). See how it is applied to summarise() in the page on Descriptive tables.\nSpecify the columns to the argument .cols = and the function(s) to apply to .fns =. Any additional arguments to provide to the .fns function can be included after a comma, still within across().\n\nacross() column selection\nSpecify the columns to the argument .cols =. You can name them individually, or use “tidyselect” helper functions. Specify the function to .fns =. Note that using the function mode demonstrated below, the function is written without its parentheses ( ).\nHere the transformation as.character() is applied to specific columns named within across().\n\nlinelist <- linelist %>% \n mutate(across(.cols = c(temp, ht_cm, wt_kg), .fns = as.character))\n\nThe “tidyselect” helper functions are available to assist you in specifying columns. They are detailed above in the section on Selecting and re-ordering columns, and they include: everything(), last_col(), where(), starts_with(), ends_with(), contains(), matches(), num_range() and any_of().\nHere is an example of how one would change all columns to character class:\n\n#to change all columns to character class\nlinelist <- linelist %>% \n mutate(across(.cols = everything(), .fns = as.character))\n\nConvert to character all columns where the name contains the string “date” (note the placement of commas and parentheses):\n\n#to change all columns to character class\nlinelist <- linelist %>% \n mutate(across(.cols = contains(\"date\"), .fns = as.character))\n\nBelow, an example of mutating the columns that are currently class POSIXct (a raw datetime class that shows timestamps) - in other words, where the function is.POSIXct() evaluates to TRUE. Then we want to apply the function as.Date() to these columns to convert them to a normal class Date.\n\nlinelist <- linelist %>% \n mutate(across(.cols = where(is.POSIXct), .fns = as.Date))\n\n\nNote that within across() we also use the function where() as is.POSIXct is evaluating to either TRUE or FALSE.\n\nNote that is.POSIXct() is from the package lubridate. Other similar “is” functions like is.character(), is.numeric(), and is.logical() are from base R.\n\n\n\nacross() functions\nYou can read the documentation with ?across for details on how to provide functions to across(). A few summary points: there are several ways to specify the function(s) to perform on a column and you can even define your own functions:\n\nYou can provide the function name alone (e.g. mean or as.character).\n\nYou can provide the function in purrr-style (e.g. ~ mean(.x, na.rm = TRUE)) (see this page).\n\nYou can specify multiple functions by providing a list (e.g. list(mean = mean, n_miss = ~ sum(is.na(.x))).\n\nIf you provide multiple functions, multiple transformed columns will be returned per input column, with unique names in the format col_fn. You can adjust how the new columns are named with the .names = argument using glue syntax (see page on Characters and strings) where {.col} and {.fn} are shorthand for the input column and function.\n\n\nHere are a few online resources on using across(): creator Hadley Wickham’s thoughts/rationale\n\n\n\ncoalesce()\nThis dplyr function finds the first non-missing value at each position. It “fills-in” missing values with the first available value in an order you specify.\nHere is an example outside the context of a data frame: Let us say you have two vectors, one containing the patient’s village of detection and another containing the patient’s village of residence. You can use coalesce to pick the first non-missing value for each index:\n\nvillage_detection <- c(\"a\", \"b\", NA, NA)\nvillage_residence <- c(\"a\", \"c\", \"a\", \"d\")\n\nvillage <- coalesce(village_detection, village_residence)\nvillage # print\n\n[1] \"a\" \"b\" \"a\" \"d\"\n\n\nThis works the same if you provide data frame columns: for each row, the function will assign the new column value with the first non-missing value in the columns you provided (in order provided).\n\nlinelist <- linelist %>% \n mutate(village = coalesce(village_detection, village_residence))\n\nThis is an example of a “row-wise” operation. For more complicated row-wise calculations, see the section below on Row-wise calculations.\n\n\nCumulative math\nIf you want a column to reflect the cumulative sum/mean/min/max etc as assessed down the rows of a dataframe to that point, use the following functions:\ncumsum() returns the cumulative sum, as shown below:\n\nsum(c(2,4,15,10)) # returns only one number\n\n[1] 31\n\ncumsum(c(2,4,15,10)) # returns the cumulative sum at each step\n\n[1] 2 6 21 31\n\n\nThis can be used in a dataframe when making a new column. For example, to calculate the cumulative number of cases per day in an outbreak, consider code like this:\n\ncumulative_case_counts <- linelist %>% # begin with case linelist\n count(date_onset) %>% # count of rows per day, as column 'n' \n mutate(cumulative_cases = cumsum(n)) # new column, of the cumulative sum at each row\n\nBelow are the first 10 rows:\n\nhead(cumulative_case_counts, 10)\n\n date_onset n cumulative_cases\n1 2012-04-15 1 1\n2 2012-05-05 1 2\n3 2012-05-08 1 3\n4 2012-05-31 1 4\n5 2012-06-02 1 5\n6 2012-06-07 1 6\n7 2012-06-14 1 7\n8 2012-06-21 1 8\n9 2012-06-24 1 9\n10 2012-06-25 1 10\n\n\nSee the page on Epidemic curves for how to plot cumulative incidence with the epicurve.\nSee also:\ncumsum(), cummean(), cummin(), cummax(), cumany(), cumall()\n\n\nUsing base R\nTo define a new column (or re-define a column) using base R, write the name of data frame, connected with $, to the new column (or the column to be modified). Use the assignment operator <- to define the new value(s). Remember that when using base R you must specify the data frame name before the column name every time (e.g. dataframe$column). Here is an example of creating the bmi column using base R:\n\nlinelist$bmi = linelist$wt_kg / (linelist$ht_cm / 100) ^ 2)\n\n\n\nAdd to pipe chain\nBelow, a new column is added to the pipe chain and some classes are converted.\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ###################################################\n # add new column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n \n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age))", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -681,7 +681,7 @@ "href": "new_pages/cleaning.html#re-code-values", "title": "8  Cleaning data and core functions", "section": "8.8 Re-code values", - "text": "8.8 Re-code values\nHere are a few scenarios where you need to re-code (change) values:\n\nto edit one specific value (e.g. one date with an incorrect year or format)\n\nto reconcile values not spelled the same\nto create a new column of categorical values\n\nto create a new column of numeric categories (e.g. age categories)\n\n\nSpecific values\nTo change values manually you can use the recode() function within the mutate() function.\nImagine there is a nonsensical date in the data (e.g. “2014-14-15”): you could fix the date manually in the raw source data, or, you could write the change into the cleaning pipeline via mutate() and recode(). The latter is more transparent and reproducible to anyone else seeking to understand or repeat your analysis.\n\n# fix incorrect values # old value # new value\nlinelist <- linelist %>% \n mutate(date_onset = recode(date_onset, \"2014-14-15\" = \"2014-04-15\"))\n\nThe mutate() line above can be read as: “mutate the column date_onset to equal the column date_onset re-coded so that OLD VALUE is changed to NEW VALUE”. Note that this pattern (OLD = NEW) for recode() is the opposite of most R patterns (new = old). The R development community is working on revising this.\nHere is another example re-coding multiple values within one column.\nIn linelist the values in the column “hospital” must be cleaned. There are several different spellings and many missing values.\n\ntable(linelist$hospital, useNA = \"always\") # print table of all unique values, including missing \n\n\n Central Hopital Central Hospital \n 11 457 \n Hospital A Hospital B \n 290 289 \n Military Hopital Military Hospital \n 32 798 \n Mitylira Hopital Mitylira Hospital \n 1 79 \n Other Port Hopital \n 907 48 \n Port Hospital St. Mark's Maternity Hospital (SMMH) \n 1756 417 \n St. Marks Maternity Hopital (SMMH) <NA> \n 11 1512 \n\n\nThe recode() command below re-defines the column “hospital” as the current column “hospital”, but with the specified recode changes. Don’t forget commas after each!\n\nlinelist <- linelist %>% \n mutate(hospital = recode(hospital,\n # for reference: OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n ))\n\nNow we see the spellings in the hospital column have been corrected and consolidated:\n\ntable(linelist$hospital, useNA = \"always\")\n\n\n Central Hospital Hospital A \n 468 290 \n Hospital B Military Hospital \n 289 910 \n Other Port Hospital \n 907 1804 \nSt. Mark's Maternity Hospital (SMMH) <NA> \n 428 1512 \n\n\nTIP: The number of spaces before and after an equals sign does not matter. Make your code easier to read by aligning the = for all or most rows. Also, consider adding a hashed comment row to clarify for future readers which side is OLD and which side is NEW. \nTIP: Sometimes a blank character value exists in a dataset (not recognized as R’s value for missing - NA). You can reference this value with two quotation marks with no space inbetween (““).\n\n\nBy logic\nBelow we demonstrate how to re-code values in a column using logic and conditions:\n\nUsing replace(), ifelse() and if_else() for simple logic\nUsing case_when() for more complex logic\n\n\n\nSimple logic\n\nreplace()\nTo re-code with simple logical criteria, you can use replace() within mutate(). replace() is a function from base R. Use a logic condition to specify the rows to change . The general syntax is:\nmutate(col_to_change = replace(col_to_change, criteria for rows, new value)).\nOne common situation to use replace() is changing just one value in one row, using an unique row identifier. Below, the gender is changed to “Female” in the row where the column case_id is “2195”.\n\n# Example: change gender of one specific observation to \"Female\" \nlinelist <- linelist %>% \n mutate(gender = replace(gender, case_id == \"2195\", \"Female\"))\n\nThe equivalent command using base R syntax and indexing brackets [ ] is below. It reads as “Change the value of the dataframe linelist‘s column gender (for the rows where linelist’s column case_id has the value ’2195’) to ‘Female’”.\n\nlinelist$gender[linelist$case_id == \"2195\"] <- \"Female\"\n\n\n\nifelse() and if_else()\nAnother tool for simple logic is ifelse() and its partner if_else(). However, in most cases for re-coding it is more clear to use case_when() (detailed below). These “if else” commands are simplified versions of an if and else programming statement. The general syntax is:\nifelse(condition, value to return if condition evaluates to TRUE, value to return if condition evaluates to FALSE)\nBelow, the column source_known is defined. Its value in a given row is set to “known” if the row’s value in column source is not missing. If the value in source is missing, then the value in source_known is set to “unknown”.\n\nlinelist <- linelist %>% \n mutate(source_known = ifelse(!is.na(source), \"known\", \"unknown\"))\n\nif_else() is a special version from dplyr that handles dates. Note that if the ‘true’ value is a date, the ‘false’ value must also qualify a date, hence using the special value NA_real_ instead of just NA.\n\n# Create a date of death column, which is NA if patient has not died.\nlinelist <- linelist %>% \n mutate(date_death = if_else(outcome == \"Death\", date_outcome, NA_real_))\n\nAvoid stringing together many ifelse commands… use case_when() instead! case_when() is much easier to read and you’ll make fewer errors.\n\n\n\n\n\n\n\n\n\nOutside of the context of a data frame, if you want to have an object used in your code switch its value, consider using switch() from base R.\n\n\n\nComplex logic\nUse dplyr’s case_when() if you are re-coding into many new groups, or if you need to use complex logic statements to re-code values. This function evaluates every row in the data frame, assess whether the rows meets specified criteria, and assigns the correct new value.\ncase_when() commands consist of statements that have a Right-Hand Side (RHS) and a Left-Hand Side (LHS) separated by a “tilde” ~. The logic criteria are in the left side and the pursuant values are in the right side of each statement. Statements are separated by commas.\nFor example, here we utilize the columns age and age_unit to create a column age_years:\n\nlinelist <- linelist %>% \n mutate(age_years = case_when(\n age_unit == \"years\" ~ age, # if age unit is years\n age_unit == \"months\" ~ age/12, # if age unit is months, divide age by 12\n is.na(age_unit) ~ age)) # if age unit is missing, assume years\n # any other circumstance, assign NA (missing)\n\nAs each row in the data is evaluated, the criteria are applied/evaluated in the order the case_when() statements are written - from top-to-bottom. If the top criteria evaluates to TRUE for a given row, the RHS value is assigned, and the remaining criteria are not even tested for that row in the data. Thus, it is best to write the most specific criteria first, and the most general last. A data row that does not meet any of the RHS criteria will be assigned NA.\nSometimes, you may with to write a final statement that assigns a value for all other scenarios not described by one of the previous lines. To do this, place TRUE on the left-side, which will capture any row that did not meet any of the previous criteria. The right-side of this statement could be assigned a value like “check me!” or missing.\nBelow is another example of case_when() used to create a new column with the patient classification, according to a case definition for confirmed and suspect cases:\n\nlinelist <- linelist %>% \n mutate(case_status = case_when(\n \n # if patient had lab test and it is positive,\n # then they are marked as a confirmed case \n ct_blood < 20 ~ \"Confirmed\",\n \n # given that a patient does not have a positive lab result,\n # if patient has a \"source\" (epidemiological link) AND has fever, \n # then they are marked as a suspect case\n !is.na(source) & fever == \"yes\" ~ \"Suspect\",\n \n # any other patient not addressed above \n # is marked for follow up\n TRUE ~ \"To investigate\"))\n\nDANGER: Values on the right-side must all be the same class - either numeric, character, date, logical, etc. To assign missing (NA), you may need to use special variations of NA such as NA_character_, NA_real_ (for numeric or POSIX), and as.Date(NA). Read more in Working with dates.\n\n\nMissing values\nBelow are special functions for handling missing values in the context of data cleaning.\nSee the page on Missing data for more detailed tips on identifying and handling missing values. For example, the is.na() function which logically tests for missingness.\nreplace_na()\nTo change missing values (NA) to a specific value, such as “Missing”, use the dplyr function replace_na() within mutate(). Note that this is used in the same manner as recode above - the name of the variable must be repeated within replace_na().\n\nlinelist <- linelist %>% \n mutate(hospital = replace_na(hospital, \"Missing\"))\n\nfct_explicit_na()\nThis is a function from the forcats package. The forcats package handles columns of class Factor. Factors are R’s way to handle ordered values such as c(\"First\", \"Second\", \"Third\") or to set the order that values (e.g. hospitals) appear in tables and plots. See the page on Factors.\nIf your data are class Factor and you try to convert NA to “Missing” by using replace_na(), you will get this error: invalid factor level, NA generated. You have tried to add “Missing” as a value, when it was not defined as a possible level of the factor, and it was rejected.\nThe easiest way to solve this is to use the forcats function fct_explicit_na() which converts a column to class factor, and converts NA values to the character “(Missing)”.\n\nlinelist %>% \n mutate(hospital = fct_explicit_na(hospital))\n\nA slower alternative would be to add the factor level using fct_expand() and then convert the missing values.\nna_if()\nTo convert a specific value to NA, use dplyr’s na_if(). The command below performs the opposite operation of replace_na(). In the example below, any values of “Missing” in the column hospital are converted to NA.\n\nlinelist <- linelist %>% \n mutate(hospital = na_if(hospital, \"Missing\"))\n\nNote: na_if() cannot be used for logic criteria (e.g. “all values > 99”) - use replace() or case_when() for this:\n\n# Convert temperatures above 40 to NA \nlinelist <- linelist %>% \n mutate(temp = replace(temp, temp > 40, NA))\n\n# Convert onset dates earlier than 1 Jan 2000 to missing\nlinelist <- linelist %>% \n mutate(date_onset = replace(date_onset, date_onset > as.Date(\"2000-01-01\"), NA))\n\n\n\nCleaning dictionary\nUse the R package matchmaker and its function match_df() to clean a data frame with a cleaning dictionary.\n\nCreate a cleaning dictionary with 3 columns:\n\nA “from” column (the incorrect value)\n\nA “to” column (the correct value)\n\nA column specifying the column for the changes to be applied (or “.global” to apply to all columns)\n\n\nNote: .global dictionary entries will be overridden by column-specific dictionary entries.\n\n\n\n\n\n\n\n\n\n\nImport the dictionary file into R. This example can be downloaded via instructions on the Download handbook and data page.\n\n\ncleaning_dict <- import(\"cleaning_dict.csv\")\n\n\nPipe the raw linelist to match_df(), specifying to dictionary = the cleaning dictionary data frame. The from = argument should be the name of the dictionary column which contains the “old” values, the by = argument should be dictionary column which contains the corresponding “new” values, and the third column lists the column in which to make the change. Use .global in the by = column to apply a change across all columns. A fourth dictionary column order can be used to specify factor order of new values.\n\nRead more details in the package documentation by running ?match_df. Note this function can take a long time to run for a large dataset.\n\nlinelist <- linelist %>% # provide or pipe your dataset\n matchmaker::match_df(\n dictionary = cleaning_dict, # name of your dictionary\n from = \"from\", # column with values to be replaced (default is col 1)\n to = \"to\", # column with final values (default is col 2)\n by = \"col\" # column with column names (default is col 3)\n )\n\nNow scroll to the right to see how values have changed - particularly gender (lowercase to uppercase), and all the symptoms columns have been transformed from yes/no to 1/0.\n\n\n\n\n\n\nNote that your column names in the cleaning dictionary must correspond to the names at this point in your cleaning script. See this online reference for the linelist package for more details.\n\nAdd to pipe chain\nBelow, some new columns and column transformations are added to the pipe chain.\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n \n # add column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n\n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age)) %>% \n \n # add column: delay to hospitalisation\n mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ###################################################\n\n # clean values of hospital column\n mutate(hospital = recode(hospital,\n # OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n )) %>% \n \n mutate(hospital = replace_na(hospital, \"Missing\")) %>% \n\n # create age_years column (from age and age_unit)\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age,\n age_unit == \"months\" ~ age/12,\n is.na(age_unit) ~ age,\n TRUE ~ NA_real_))", + "text": "8.8 Re-code values\nHere are a few scenarios where you need to re-code (change) values:\n\nto edit one specific value (e.g. one date with an incorrect year or format).\n\nto reconcile values not spelled the same.\nto create a new column of categorical values.\n\nto create a new column of numeric categories (e.g. age categories).\n\n\nSpecific values\nTo change values manually you can use the recode() function within the mutate() function.\nImagine there is a nonsensical date in the data (e.g. “2014-14-15”): you could fix the date manually in the raw source data, or, you could write the change into the cleaning pipeline via mutate() and recode(). The latter is more transparent and reproducible to anyone else seeking to understand or repeat your analysis.\n\n# fix incorrect values # old value # new value\nlinelist <- linelist %>% \n mutate(date_onset = recode(date_onset, \"2014-14-15\" = \"2014-04-15\"))\n\nThe mutate() line above can be read as: “mutate the column date_onset to equal the column date_onset re-coded so that OLD VALUE is changed to NEW VALUE”. Note that this pattern (OLD = NEW) for recode() is the opposite of most R patterns (new = old). The R development community is working on revising this.\nHere is another example re-coding multiple values within one column.\nIn linelist the values in the column “hospital” must be cleaned. There are several different spellings and many missing values.\n\ntable(linelist$hospital, useNA = \"always\") # print table of all unique values, including missing \n\n\n Central Hopital Central Hospital \n 11 457 \n Hospital A Hospital B \n 290 289 \n Military Hopital Military Hospital \n 32 798 \n Mitylira Hopital Mitylira Hospital \n 1 79 \n Other Port Hopital \n 907 48 \n Port Hospital St. Mark's Maternity Hospital (SMMH) \n 1756 417 \n St. Marks Maternity Hopital (SMMH) <NA> \n 11 1512 \n\n\nThe recode() command below re-defines the column “hospital” as the current column “hospital”, but with the specified recode changes. Don’t forget commas after each!\n\nlinelist <- linelist %>% \n mutate(hospital = recode(hospital,\n # for reference: OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n ))\n\nNow we see the spellings in the hospital column have been corrected and consolidated:\n\ntable(linelist$hospital, useNA = \"always\")\n\n\n Central Hospital Hospital A \n 468 290 \n Hospital B Military Hospital \n 289 910 \n Other Port Hospital \n 907 1804 \nSt. Mark's Maternity Hospital (SMMH) <NA> \n 428 1512 \n\n\nTIP: The number of spaces before and after an equals sign does not matter. Make your code easier to read by aligning the = for all or most rows. Also, consider adding a hashed comment row to clarify for future readers which side is OLD and which side is NEW. \nTIP: Sometimes a blank character value exists in a dataset (not recognized as R’s value for missing - NA). You can reference this value with two quotation marks with no space inbetween (““).\n\n\nBy logic\nBelow we demonstrate how to re-code values in a column using logic and conditions:\n\nUsing replace(), ifelse() and if_else() for simple logic.\nUsing case_when() for more complex logic.\n\n\n\nSimple logic\n\nreplace()\nTo re-code with simple logical criteria, you can use replace() within mutate(). replace() is a function from base R. Use a logic condition to specify the rows to change . The general syntax is:\n\nmutate(col_to_change = replace(col_to_change, criteria for rows, new value))\n\nOne common situation to use replace() is changing just one value in one row, using an unique row identifier. Below, the gender is changed to “Female” in the row where the column case_id is “2195”.\n\n# Example: change gender of one specific observation to \"Female\" \nlinelist <- linelist %>% \n mutate(gender = replace(gender, case_id == \"2195\", \"Female\"))\n\nThe equivalent command using base R syntax and indexing brackets [ ] is below. It reads as “Change the value of the dataframe linelist‘s column gender (for the rows where linelist’s column case_id has the value ’2195’) to ‘Female’”.\n\nlinelist$gender[linelist$case_id == \"2195\"] <- \"Female\"\n\n\n\nifelse() and if_else()\nAnother tool for simple logic is ifelse() and its partner if_else(). However, in most cases for re-coding it is more clear to use case_when() (detailed below). These “if else” commands are simplified versions of an if and else programming statement. The general syntax is:\nifelse(condition, value to return if condition evaluates to TRUE, value to return if condition evaluates to FALSE)\nBelow, the column source_known is defined. Its value in a given row is set to “known” if the row’s value in column source is not missing. If the value in source is missing, then the value in source_known is set to “unknown”.\n\nlinelist <- linelist %>% \n mutate(source_known = ifelse(!is.na(source), \"known\", \"unknown\"))\n\nif_else() is a special version from dplyr that handles dates. Note that if the ‘true’ value is a date, the ‘false’ value must also qualify a date, hence using the special value NA_real_ instead of just NA.\n\n# Create a date of death column, which is NA if patient has not died.\nlinelist <- linelist %>% \n mutate(date_death = if_else(outcome == \"Death\", date_outcome, NA_real_))\n\nAvoid stringing together many ifelse commands… use case_when() instead! case_when() is much easier to read and you’ll make fewer errors.\n\n\n\n\n\n\n\n\n\nOutside of the context of a data frame, if you want to have an object used in your code switch its value, consider using switch() from base R.\n\n\n\nComplex logic\nUse dplyr’s case_when() if you are re-coding into many new groups, or if you need to use complex logic statements to re-code values. This function evaluates every row in the data frame, assess whether the rows meets specified criteria, and assigns the correct new value.\ncase_when() commands consist of statements that have a Right-Hand Side (RHS) and a Left-Hand Side (LHS) separated by a “tilde” ~. The logic criteria are in the left side and the pursuant values are in the right side of each statement. Statements are separated by commas.\nFor example, here we utilize the columns age and age_unit to create a column age_years:\n\nlinelist <- linelist %>% \n mutate(age_years = case_when(\n age_unit == \"years\" ~ age, # if age unit is years\n age_unit == \"months\" ~ age/12, # if age unit is months, divide age by 12\n is.na(age_unit) ~ age)) # if age unit is missing, assume years\n # any other circumstance, assign NA (missing)\n\nAs each row in the data is evaluated, the criteria are applied/evaluated in the order the case_when() statements are written, from top-to-bottom. If the top criteria evaluates to TRUE for a given row, the RHS value is assigned, and the remaining criteria are not even tested for that row in the data. Thus, it is best to write the most specific criteria first, and the most general last. A data row that does not meet any of the RHS criteria will be assigned NA.\nSometimes, you may with to write a final statement that assigns a value for all other scenarios not described by one of the previous lines. To do this, place TRUE on the left-side, which will capture any row that did not meet any of the previous criteria. The right-side of this statement could be assigned a value like “check me!” or missing.\nBelow is another example of case_when() used to create a new column with the patient classification, according to a case definition for confirmed and suspect cases:\n\nlinelist <- linelist %>% \n mutate(case_status = case_when(\n \n # if patient had lab test and it is positive,\n # then they are marked as a confirmed case \n ct_blood < 20 ~ \"Confirmed\",\n \n # given that a patient does not have a positive lab result,\n # if patient has a \"source\" (epidemiological link) AND has fever, \n # then they are marked as a suspect case\n !is.na(source) & fever == \"yes\" ~ \"Suspect\",\n \n # any other patient not addressed above \n # is marked for follow up\n TRUE ~ \"To investigate\"))\n\nDANGER: Values on the right-side must all be the same class - either numeric, character, date, logical, etc. To assign missing (NA), you may need to use special variations of NA such as NA_character_, NA_real_ (for numeric or POSIX), and as.Date(NA). Read more in Working with dates.\n\n\nMissing values\nBelow are special functions for handling missing values in the context of data cleaning.\nSee the page on Missing data for more detailed tips on identifying and handling missing values. For example, the is.na() function which logically tests for missingness.\nreplace_na()\nTo change missing values (NA) to a specific value, such as “Missing”, use the dplyr function replace_na() within mutate(). Note that this is used in the same manner as recode above - the name of the variable must be repeated within replace_na().\n\nlinelist <- linelist %>% \n mutate(hospital = replace_na(hospital, \"Missing\"))\n\nfct_na_value_to_level()\nThis is a function from the forcats package. The forcats package handles columns of class Factor. Factors are R’s way to handle ordered values such as c(\"First\", \"Second\", \"Third\") or to set the order that values (e.g. hospitals) appear in tables and plots. See the page on Factors.\nIf your data are class Factor and you try to convert NA to “Missing” by using replace_na(), you will get this error: invalid factor level, NA generated. You have tried to add “Missing” as a value, when it was not defined as a possible level of the factor, and it was rejected.\nThe easiest way to solve this is to use the forcats function fct_na_value_to_level() which converts a column to class factor, and converts NA values to the character “(Missing)”.\n\nlinelist %>% \n mutate(hospital = fct_na_value_to_level(hospital))\n\nA slower alternative would be to add the factor level using fct_expand() and then convert the missing values.\nna_if()\nTo convert a specific value to NA, use dplyr’s na_if(). The command below performs the opposite operation of replace_na(). In the example below, any values of “Missing” in the column hospital are converted to NA.\n\nlinelist <- linelist %>% \n mutate(hospital = na_if(hospital, \"Missing\"))\n\nNote: na_if() cannot be used for logic criteria (e.g. “all values > 99”) - use replace() or case_when() for this:\n\n# Convert temperatures above 40 to NA \nlinelist <- linelist %>% \n mutate(temp = replace(temp, temp > 40, NA))\n\n# Convert onset dates earlier than 1 Jan 2000 to missing\nlinelist <- linelist %>% \n mutate(date_onset = replace(date_onset, date_onset > as.Date(\"2000-01-01\"), NA))\n\n\n\nCleaning dictionary\nUse the R package matchmaker and its function match_df() to clean a data frame with a cleaning dictionary.\n\nCreate a cleaning dictionary with 3 columns:\n\nA “from” column (the incorrect value).\n\nA “to” column (the correct value).\n\nA column specifying the column for the changes to be applied (or “.global” to apply to all columns).\n\n\nNote: .global dictionary entries will be overridden by column-specific dictionary entries.\n\n\n\n\n\n\n\n\n\n\nImport the dictionary file into R. This example can be downloaded via instructions on the Download handbook and data page.\n\n\ncleaning_dict <- import(\"cleaning_dict.csv\")\n\n\nPipe the raw linelist to match_df(), specifying to dictionary = the cleaning dictionary data frame. The from = argument should be the name of the dictionary column which contains the “old” values, the by = argument should be dictionary column which contains the corresponding “new” values, and the third column lists the column in which to make the change. Use .global in the by = column to apply a change across all columns. A fourth dictionary column order can be used to specify factor order of new values.\n\nRead more details in the package documentation by running ?match_df. Note this function can take a long time to run for a large dataset.\n\nlinelist <- linelist %>% # provide or pipe your dataset\n matchmaker::match_df(\n dictionary = cleaning_dict, # name of your dictionary\n from = \"from\", # column with values to be replaced (default is col 1)\n to = \"to\", # column with final values (default is col 2)\n by = \"col\" # column with column names (default is col 3)\n )\n\nNow scroll to the right to see how values have changed - particularly gender (lowercase to uppercase), and all the symptoms columns have been transformed from yes/no to 1/0.\n\n\n\n\n\n\nNote that your column names in the cleaning dictionary must correspond to the names at this point in your cleaning script. See this online reference for the linelist package for more details.\n\nAdd to pipe chain\nBelow, some new columns and column transformations are added to the pipe chain.\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n \n # add column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n\n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age)) %>% \n \n # add column: delay to hospitalisation\n mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ###################################################\n\n # clean values of hospital column\n mutate(hospital = recode(hospital,\n # OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n )) %>% \n \n mutate(hospital = replace_na(hospital, \"Missing\")) %>% \n\n # create age_years column (from age and age_unit)\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age,\n age_unit == \"months\" ~ age/12,\n is.na(age_unit) ~ age,\n TRUE ~ NA_real_))", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -692,7 +692,7 @@ "href": "new_pages/cleaning.html#num_cats", "title": "8  Cleaning data and core functions", "section": "8.9 Numeric categories", - "text": "8.9 Numeric categories\nHere we describe some special approaches for creating categories from numerical columns. Common examples include age categories, groups of lab values, etc. Here we will discuss:\n\nage_categories(), from the epikit package\n\ncut(), from base R\n\ncase_when()\n\nquantile breaks with quantile() and ntile()\n\n\nReview distribution\nFor this example we will create an age_cat column using the age_years column.\n\n#check the class of the linelist variable age\nclass(linelist$age_years)\n\n[1] \"numeric\"\n\n\nFirst, examine the distribution of your data, to make appropriate cut-points. See the page on ggplot basics.\n\n# examine the distribution\nhist(linelist$age_years)\n\n\n\n\n\n\n\n\n\nsummary(linelist$age_years, na.rm=T)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.04 23.00 84.00 107 \n\n\nCAUTION: Sometimes, numeric variables will import as class “character”. This occurs if there are non-numeric characters in some of the values, for example an entry of “2 months” for age, or (depending on your R locale settings) if a comma is used in the decimals place (e.g. “4,5” to mean four and one half years)..\n\n\n\nage_categories()\nWith the epikit package, you can use the age_categories() function to easily categorize and label numeric columns (note: this function can be applied to non-age numeric variables too). As a bonum, the output column is automatically an ordered factor.\nHere are the required inputs:\n\nA numeric vector (column)\n\nThe breakers = argument - provide a numeric vector of break points for the new groups\n\nFirst, the simplest example:\n\n# Simple example\n################\npacman::p_load(epikit) # load package\n\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories( # create new column\n age_years, # numeric column to make groups from\n breakers = c(0, 5, 10, 15, 20, # break points\n 30, 40, 50, 60, 70)))\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-4 5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-69 70+ <NA> \n 1227 1223 1048 827 1216 597 251 78 27 7 107 \n\n\nThe break values you specify are by default the lower bounds - that is, they are included in the “higher” group / the groups are “open” on the lower/left side. As shown below, you can add 1 to each break value to achieve groups that are open at the top/right.\n\n# Include upper ends for the same categories\n############################################\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories(\n age_years, \n breakers = c(0, 6, 11, 16, 21, 31, 41, 51, 61, 71)))\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-5 6-10 11-15 16-20 21-30 31-40 41-50 51-60 61-70 71+ <NA> \n 1469 1195 1040 770 1149 547 231 70 24 6 107 \n\n\nYou can adjust how the labels are displayed with separator =. The default is “-”\nYou can adjust how the top numbers are handled, with the ceiling = arguemnt. To set an upper cut-off set ceiling = TRUE. In this use, the highest break value provided is a “ceiling” and a category “XX+” is not created. Any values above highest break value (or to upper =, if defined) are categorized as NA. Below is an example with ceiling = TRUE, so that there is no category of XX+ and values above 70 (the highest break value) are assigned as NA.\n\n# With ceiling set to TRUE\n##########################\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories(\n age_years, \n breakers = c(0, 5, 10, 15, 20, 30, 40, 50, 60, 70),\n ceiling = TRUE)) # 70 is ceiling, all above become NA\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-4 5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-70 <NA> \n 1227 1223 1048 827 1216 597 251 78 28 113 \n\n\nAlternatively, instead of breakers =, you can provide all of lower =, upper =, and by =:\n\nlower = The lowest number you want considered - default is 0\n\nupper = The highest number you want considered\n\nby = The number of years between groups\n\n\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories(\n age_years, \n lower = 0,\n upper = 100,\n by = 10))\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-9 10-19 20-29 30-39 40-49 50-59 60-69 70-79 80-89 90-99 100+ <NA> \n 2450 1875 1216 597 251 78 27 6 1 0 0 107 \n\n\nSee the function’s Help page for more details (enter ?age_categories in the R console).\n\n\n\ncut()\ncut() is a base R alternative to age_categories(), but I think you will see why age_categories() was developed to simplify this process. Some notable differences from age_categories() are:\n\nYou do not need to install/load another package\n\nYou can specify whether groups are open/closed on the right/left\n\nYou must provide accurate labels yourself\n\nIf you want 0 included in the lowest group you must specify this\n\nThe basic syntax within cut() is to first provide the numeric column to be cut (age_years), and then the breaks argument, which is a numeric vector c() of break points. Using cut(), the resulting column is an ordered factor.\nBy default, the categorization occurs so that the right/upper side is “open” and inclusive (and the left/lower side is “closed” or exclusive). This is the opposite behavior from the age_categories() function. The default labels use the notation “(A, B]”, which means A is not included but B is. Reverse this behavior by providing the right = TRUE argument.\nThus, by default, “0” values are excluded from the lowest group, and categorized as NA! “0” values could be infants coded as age 0 so be careful! To change this, add the argument include.lowest = TRUE so that any “0” values will be included in the lowest group. The automatically-generated label for the lowest category will then be “[A],B]”. Note that if you include the include.lowest = TRUE argument and right = TRUE, the extreme inclusion will now apply to the highest break point value and category, not the lowest.\nYou can provide a vector of customized labels using the labels = argument. As these are manually written, be very careful to ensure they are accurate! Check your work using cross-tabulation, as described below.\nAn example of cut() applied to age_years to make the new variable age_cat is below:\n\n# Create new variable, by cutting the numeric age variable\n# lower break is excluded but upper break is included in each category\nlinelist <- linelist %>% \n mutate(\n age_cat = cut(\n age_years,\n breaks = c(0, 5, 10, 15, 20,\n 30, 50, 70, 100),\n include.lowest = TRUE # include 0 in lowest group\n ))\n\n# tabulate the number of observations per group\ntable(linelist$age_cat, useNA = \"always\")\n\n\n [0,5] (5,10] (10,15] (15,20] (20,30] (30,50] (50,70] (70,100] \n 1469 1195 1040 770 1149 778 94 6 \n <NA> \n 107 \n\n\nCheck your work!!! Verify that each age value was assigned to the correct category by cross-tabulating the numeric and category columns. Examine assignment of boundary values (e.g. 15, if neighboring categories are 10-15 and 16-20).\n\n# Cross tabulation of the numeric and category columns. \ntable(\"Numeric Values\" = linelist$age_years, # names specified in table for clarity.\n \"Categories\" = linelist$age_cat,\n useNA = \"always\") # don't forget to examine NA values\n\n Categories\nNumeric Values [0,5] (5,10] (10,15] (15,20] (20,30] (30,50] (50,70]\n 0 136 0 0 0 0 0 0\n 0.0833333333333333 1 0 0 0 0 0 0\n 0.25 2 0 0 0 0 0 0\n 0.333333333333333 6 0 0 0 0 0 0\n 0.416666666666667 1 0 0 0 0 0 0\n 0.5 6 0 0 0 0 0 0\n 0.583333333333333 3 0 0 0 0 0 0\n 0.666666666666667 3 0 0 0 0 0 0\n 0.75 3 0 0 0 0 0 0\n 0.833333333333333 1 0 0 0 0 0 0\n 0.916666666666667 1 0 0 0 0 0 0\n 1 275 0 0 0 0 0 0\n 1.5 2 0 0 0 0 0 0\n 2 308 0 0 0 0 0 0\n 3 246 0 0 0 0 0 0\n 4 233 0 0 0 0 0 0\n 5 242 0 0 0 0 0 0\n 6 0 241 0 0 0 0 0\n 7 0 256 0 0 0 0 0\n 8 0 239 0 0 0 0 0\n 9 0 245 0 0 0 0 0\n 10 0 214 0 0 0 0 0\n 11 0 0 220 0 0 0 0\n 12 0 0 224 0 0 0 0\n 13 0 0 191 0 0 0 0\n 14 0 0 199 0 0 0 0\n 15 0 0 206 0 0 0 0\n 16 0 0 0 186 0 0 0\n 17 0 0 0 164 0 0 0\n 18 0 0 0 141 0 0 0\n 19 0 0 0 130 0 0 0\n 20 0 0 0 149 0 0 0\n 21 0 0 0 0 158 0 0\n 22 0 0 0 0 149 0 0\n 23 0 0 0 0 125 0 0\n 24 0 0 0 0 144 0 0\n 25 0 0 0 0 107 0 0\n 26 0 0 0 0 100 0 0\n 27 0 0 0 0 117 0 0\n 28 0 0 0 0 85 0 0\n 29 0 0 0 0 82 0 0\n 30 0 0 0 0 82 0 0\n 31 0 0 0 0 0 68 0\n 32 0 0 0 0 0 84 0\n 33 0 0 0 0 0 78 0\n 34 0 0 0 0 0 58 0\n 35 0 0 0 0 0 58 0\n 36 0 0 0 0 0 33 0\n 37 0 0 0 0 0 46 0\n 38 0 0 0 0 0 45 0\n 39 0 0 0 0 0 45 0\n 40 0 0 0 0 0 32 0\n 41 0 0 0 0 0 34 0\n 42 0 0 0 0 0 26 0\n 43 0 0 0 0 0 31 0\n 44 0 0 0 0 0 24 0\n 45 0 0 0 0 0 27 0\n 46 0 0 0 0 0 25 0\n 47 0 0 0 0 0 16 0\n 48 0 0 0 0 0 21 0\n 49 0 0 0 0 0 15 0\n 50 0 0 0 0 0 12 0\n 51 0 0 0 0 0 0 13\n 52 0 0 0 0 0 0 7\n 53 0 0 0 0 0 0 4\n 54 0 0 0 0 0 0 6\n 55 0 0 0 0 0 0 9\n 56 0 0 0 0 0 0 7\n 57 0 0 0 0 0 0 9\n 58 0 0 0 0 0 0 6\n 59 0 0 0 0 0 0 5\n 60 0 0 0 0 0 0 4\n 61 0 0 0 0 0 0 2\n 62 0 0 0 0 0 0 1\n 63 0 0 0 0 0 0 5\n 64 0 0 0 0 0 0 1\n 65 0 0 0 0 0 0 5\n 66 0 0 0 0 0 0 3\n 67 0 0 0 0 0 0 2\n 68 0 0 0 0 0 0 1\n 69 0 0 0 0 0 0 3\n 70 0 0 0 0 0 0 1\n 72 0 0 0 0 0 0 0\n 73 0 0 0 0 0 0 0\n 76 0 0 0 0 0 0 0\n 84 0 0 0 0 0 0 0\n <NA> 0 0 0 0 0 0 0\n Categories\nNumeric Values (70,100] <NA>\n 0 0 0\n 0.0833333333333333 0 0\n 0.25 0 0\n 0.333333333333333 0 0\n 0.416666666666667 0 0\n 0.5 0 0\n 0.583333333333333 0 0\n 0.666666666666667 0 0\n 0.75 0 0\n 0.833333333333333 0 0\n 0.916666666666667 0 0\n 1 0 0\n 1.5 0 0\n 2 0 0\n 3 0 0\n 4 0 0\n 5 0 0\n 6 0 0\n 7 0 0\n 8 0 0\n 9 0 0\n 10 0 0\n 11 0 0\n 12 0 0\n 13 0 0\n 14 0 0\n 15 0 0\n 16 0 0\n 17 0 0\n 18 0 0\n 19 0 0\n 20 0 0\n 21 0 0\n 22 0 0\n 23 0 0\n 24 0 0\n 25 0 0\n 26 0 0\n 27 0 0\n 28 0 0\n 29 0 0\n 30 0 0\n 31 0 0\n 32 0 0\n 33 0 0\n 34 0 0\n 35 0 0\n 36 0 0\n 37 0 0\n 38 0 0\n 39 0 0\n 40 0 0\n 41 0 0\n 42 0 0\n 43 0 0\n 44 0 0\n 45 0 0\n 46 0 0\n 47 0 0\n 48 0 0\n 49 0 0\n 50 0 0\n 51 0 0\n 52 0 0\n 53 0 0\n 54 0 0\n 55 0 0\n 56 0 0\n 57 0 0\n 58 0 0\n 59 0 0\n 60 0 0\n 61 0 0\n 62 0 0\n 63 0 0\n 64 0 0\n 65 0 0\n 66 0 0\n 67 0 0\n 68 0 0\n 69 0 0\n 70 0 0\n 72 1 0\n 73 3 0\n 76 1 0\n 84 1 0\n <NA> 0 107\n\n\nRe-labeling NA values\nYou may want to assign NA values a label such as “Missing”. Because the new column is class Factor (restricted values), you cannot simply mutate it with replace_na(), as this value will be rejected. Instead, use fct_explicit_na() from forcats as explained in the Factors page.\n\nlinelist <- linelist %>% \n \n # cut() creates age_cat, automatically of class Factor \n mutate(age_cat = cut(\n age_years,\n breaks = c(0, 5, 10, 15, 20, 30, 50, 70, 100), \n right = FALSE,\n include.lowest = TRUE, \n labels = c(\"0-4\", \"5-9\", \"10-14\", \"15-19\", \"20-29\", \"30-49\", \"50-69\", \"70-100\")),\n \n # make missing values explicit\n age_cat = fct_explicit_na(\n age_cat,\n na_level = \"Missing age\") # you can specify the label\n ) \n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `age_cat = fct_explicit_na(age_cat, na_level = \"Missing age\")`.\nCaused by warning:\n! `fct_explicit_na()` was deprecated in forcats 1.0.0.\nℹ Please use `fct_na_value_to_level()` instead.\n\n# table to view counts\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-4 5-9 10-14 15-19 20-29 30-49 \n 1227 1223 1048 827 1216 848 \n 50-69 70-100 Missing age <NA> \n 105 7 107 0 \n\n\nQuickly make breaks and labels\nFor a fast way to make breaks and label vectors, use something like below. See the R basics page for references on seq() and rep().\n\n# Make break points from 0 to 90 by 5\nage_seq = seq(from = 0, to = 90, by = 5)\nage_seq\n\n# Make labels for the above categories, assuming default cut() settings\nage_labels = paste0(age_seq + 1, \"-\", age_seq + 5)\nage_labels\n\n# check that both vectors are the same length\nlength(age_seq) == length(age_labels)\n\nRead more about cut() in its Help page by entering ?cut in the R console.\n\n\nQuantile breaks\nIn common understanding, “quantiles” or “percentiles” typically refer to a value below which a proportion of values fall. For example, the 95th percentile of ages in linelist would be the age below which 95% of the age fall.\nHowever in common speech, “quartiles” and “deciles” can also refer to the groups of data as equally divided into 4, or 10 groups (note there will be one more break point than group).\nTo get quantile break points, you can use quantile() from the stats package from base R. You provide a numeric vector (e.g. a column in a dataset) and vector of numeric probability values ranging from 0 to 1.0. The break points are returned as a numeric vector. Explore the details of the statistical methodologies by entering ?quantile.\n\nIf your input numeric vector has any missing values it is best to set na.rm = TRUE\n\nSet names = FALSE to get an un-named numeric vector\n\n\nquantile(linelist$age_years, # specify numeric vector to work on\n probs = c(0, .25, .50, .75, .90, .95), # specify the percentiles you want\n na.rm = TRUE) # ignore missing values \n\n 0% 25% 50% 75% 90% 95% \n 0 6 13 23 33 41 \n\n\nYou can use the results of quantile() as break points in age_categories() or cut(). Below we create a new column deciles using cut() where the breaks are defined using quantiles() on age_years. Below, we display the results using tabyl() from janitor so you can see the percentages (see the Descriptive tables page). Note how they are not exactly 10% in each group.\n\nlinelist %>% # begin with linelist\n mutate(deciles = cut(age_years, # create new column decile as cut() on column age_years\n breaks = quantile( # define cut breaks using quantile()\n age_years, # operate on age_years\n probs = seq(0, 1, by = 0.1), # 0.0 to 1.0 by 0.1\n na.rm = TRUE), # ignore missing values\n include.lowest = TRUE)) %>% # for cut() include age 0\n janitor::tabyl(deciles) # pipe to table to display\n\n deciles n percent valid_percent\n [0,2] 748 0.11319613 0.11505922\n (2,5] 721 0.10911017 0.11090601\n (5,7] 497 0.07521186 0.07644978\n (7,10] 698 0.10562954 0.10736810\n (10,13] 635 0.09609564 0.09767728\n (13,17] 755 0.11425545 0.11613598\n (17,21] 578 0.08746973 0.08890940\n (21,26] 625 0.09458232 0.09613906\n (26,33] 596 0.09019370 0.09167820\n (33,84] 648 0.09806295 0.09967697\n <NA> 107 0.01619249 NA\n\n\n\n\nEvenly-sized groups\nAnother tool to make numeric groups is the the dplyr function ntile(), which attempts to break your data into n evenly-sized groups - but be aware that unlike with quantile() the same value could appear in more than one group. Provide the numeric vector and then the number of groups. The values in the new column created is just group “numbers” (e.g. 1 to 10), not the range of values themselves as when using cut().\n\n# make groups with ntile()\nntile_data <- linelist %>% \n mutate(even_groups = ntile(age_years, 10))\n\n# make table of counts and proportions by group\nntile_table <- ntile_data %>% \n janitor::tabyl(even_groups)\n \n# attach min/max values to demonstrate ranges\nntile_ranges <- ntile_data %>% \n group_by(even_groups) %>% \n summarise(\n min = min(age_years, na.rm=T),\n max = max(age_years, na.rm=T)\n )\n\nWarning: There were 2 warnings in `summarise()`.\nThe first warning was:\nℹ In argument: `min = min(age_years, na.rm = T)`.\nℹ In group 11: `even_groups = NA`.\nCaused by warning in `min()`:\n! no non-missing arguments to min; returning Inf\nℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.\n\n# combine and print - note that values are present in multiple groups\nleft_join(ntile_table, ntile_ranges, by = \"even_groups\")\n\n even_groups n percent valid_percent min max\n 1 651 0.09851695 0.10013844 0 2\n 2 650 0.09836562 0.09998462 2 5\n 3 650 0.09836562 0.09998462 5 7\n 4 650 0.09836562 0.09998462 7 10\n 5 650 0.09836562 0.09998462 10 13\n 6 650 0.09836562 0.09998462 13 17\n 7 650 0.09836562 0.09998462 17 21\n 8 650 0.09836562 0.09998462 21 26\n 9 650 0.09836562 0.09998462 26 33\n 10 650 0.09836562 0.09998462 33 84\n NA 107 0.01619249 NA Inf -Inf\n\n\n\n\n\ncase_when()\nIt is possible to use the dplyr function case_when() to create categories from a numeric column, but it is easier to use age_categories() from epikit or cut() because these will create an ordered factor automatically.\nIf using case_when(), please review the proper use as described earlier in the Re-code values section of this page. Also be aware that all right-hand side values must be of the same class. Thus, if you want NA on the right-side you should either write “Missing” or use the special NA value NA_character_.\n\n\nAdd to pipe chain\nBelow, code to create two categorical age columns is added to the cleaning pipe chain:\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n\n # add column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n\n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age)) %>% \n \n # add column: delay to hospitalisation\n mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% \n \n # clean values of hospital column\n mutate(hospital = recode(hospital,\n # OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n )) %>% \n \n mutate(hospital = replace_na(hospital, \"Missing\")) %>% \n\n # create age_years column (from age and age_unit)\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age,\n age_unit == \"months\" ~ age/12,\n is.na(age_unit) ~ age)) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ################################################### \n mutate(\n # age categories: custom\n age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),\n \n # age categories: 0 to 85 by 5s\n age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5)))", + "text": "8.9 Numeric categories\nHere we describe some special approaches for creating categories from numerical columns. Common examples include age categories, groups of lab values, etc. Here we will discuss:\n\nage_categories(), from the epikit package.\n\ncut(), from base R.\n\ncase_when().\n\nquantile breaks with quantile() and ntile().\n\n\nReview distribution\nFor this example we will create an age_cat column using the age_years column.\n\n#check the class of the linelist variable age\nclass(linelist$age_years)\n\n[1] \"numeric\"\n\n\nFirst, examine the distribution of your data, to make appropriate cut-points. See the page on ggplot basics.\n\n# examine the distribution\nhist(linelist$age_years)\n\n\n\n\n\n\n\n\n\nsummary(linelist$age_years, na.rm=T)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.04 23.00 84.00 107 \n\n\nCAUTION: Sometimes, numeric variables will import as class “character”. This occurs if there are non-numeric characters in some of the values, for example an entry of “2 months” for age, or (depending on your R locale settings) if a comma is used in the decimals place (e.g. “4,5” to mean four and one half years)..\n\n\n\nage_categories()\nWith the epikit package, you can use the age_categories() function to easily categorize and label numeric columns (note: this function can be applied to non-age numeric variables too). As a bonum, the output column is automatically an ordered factor.\nHere are the required inputs:\n\nA numeric vector (column)\n\nThe breakers = argument - provide a numeric vector of break points for the new groups\n\nFirst, the simplest example:\n\n# Simple example\n################\npacman::p_load(epikit) # load package\n\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories( # create new column\n age_years, # numeric column to make groups from\n breakers = c(0, 5, 10, 15, 20, # break points\n 30, 40, 50, 60, 70)))\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-4 5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-69 70+ <NA> \n 1227 1223 1048 827 1216 597 251 78 27 7 107 \n\n\nThe break values you specify are by default the lower bounds - that is, they are included in the “higher” group / the groups are “open” on the lower/left side. As shown below, you can add 1 to each break value to achieve groups that are open at the top/right.\n\n# Include upper ends for the same categories\n############################################\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories(\n age_years, \n breakers = c(0, 6, 11, 16, 21, 31, 41, 51, 61, 71)))\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-5 6-10 11-15 16-20 21-30 31-40 41-50 51-60 61-70 71+ <NA> \n 1469 1195 1040 770 1149 547 231 70 24 6 107 \n\n\nYou can adjust how the labels are displayed with separator =. The default is “-”\nYou can adjust how the top numbers are handled, with the ceiling = arguemnt. To set an upper cut-off set ceiling = TRUE. In this use, the highest break value provided is a “ceiling” and a category “XX+” is not created. Any values above highest break value (or to upper =, if defined) are categorized as NA. Below is an example with ceiling = TRUE, so that there is no category of XX+ and values above 70 (the highest break value) are assigned as NA.\n\n# With ceiling set to TRUE\n##########################\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories(\n age_years, \n breakers = c(0, 5, 10, 15, 20, 30, 40, 50, 60, 70),\n ceiling = TRUE)) # 70 is ceiling, all above become NA\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-4 5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-70 <NA> \n 1227 1223 1048 827 1216 597 251 78 28 113 \n\n\nAlternatively, instead of breakers =, you can provide all of lower =, upper =, and by =:\n\nlower = The lowest number you want considered - default is 0\n\nupper = The highest number you want considered\n\nby = The number of years between groups\n\n\nlinelist <- linelist %>% \n mutate(\n age_cat = age_categories(\n age_years, \n lower = 0,\n upper = 100,\n by = 10))\n\n# show table\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-9 10-19 20-29 30-39 40-49 50-59 60-69 70-79 80-89 90-99 100+ <NA> \n 2450 1875 1216 597 251 78 27 6 1 0 0 107 \n\n\nSee the function’s Help page for more details (enter ?age_categories in the R console).\n\n\n\ncut()\ncut() is a base R alternative to age_categories(), but I think you will see why age_categories() was developed to simplify this process. Some notable differences from age_categories() are:\n\nYou do not need to install/load another package.\n\nYou can specify whether groups are open/closed on the right/left.\n\nYou must provide accurate labels yourself.\n\nIf you want 0 included in the lowest group you must specify this.\n\nThe basic syntax within cut() is to first provide the numeric column to be cut (age_years), and then the breaks argument, which is a numeric vector c() of break points. Using cut(), the resulting column is an ordered factor.\nBy default, the categorization occurs so that the right/upper side is “open” and inclusive (and the left/lower side is “closed” or exclusive). This is the opposite behavior from the age_categories() function. The default labels use the notation “(A, B]”, which means A is not included but B is.Reverse this behavior by providing the right = TRUE argument.\nThus, by default, “0” values are excluded from the lowest group, and categorized as NA! “0” values could be infants coded as age 0 so be careful! To change this, add the argument include.lowest = TRUE so that any “0” values will be included in the lowest group. The automatically-generated label for the lowest category will then be “[A],B]”. Note that if you include the include.lowest = TRUE argument and right = TRUE, the extreme inclusion will now apply to the highest break point value and category, not the lowest.\nYou can provide a vector of customized labels using the labels = argument. As these are manually written, be very careful to ensure they are accurate! Check your work using cross-tabulation, as described below.\nAn example of cut() applied to age_years to make the new variable age_cat is below:\n\n# Create new variable, by cutting the numeric age variable\n# lower break is excluded but upper break is included in each category\nlinelist <- linelist %>% \n mutate(\n age_cat = cut(\n age_years,\n breaks = c(0, 5, 10, 15, 20,\n 30, 50, 70, 100),\n include.lowest = TRUE # include 0 in lowest group\n ))\n\n# tabulate the number of observations per group\ntable(linelist$age_cat, useNA = \"always\")\n\n\n [0,5] (5,10] (10,15] (15,20] (20,30] (30,50] (50,70] (70,100] \n 1469 1195 1040 770 1149 778 94 6 \n <NA> \n 107 \n\n\nCheck your work!!! Verify that each age value was assigned to the correct category by cross-tabulating the numeric and category columns. Examine assignment of boundary values (e.g. 15, if neighboring categories are 10-15 and 16-20).\n\n# Cross tabulation of the numeric and category columns. \ntable(\"Numeric Values\" = linelist$age_years, # names specified in table for clarity.\n \"Categories\" = linelist$age_cat,\n useNA = \"always\") # don't forget to examine NA values\n\n Categories\nNumeric Values [0,5] (5,10] (10,15] (15,20] (20,30] (30,50] (50,70]\n 0 136 0 0 0 0 0 0\n 0.0833333333333333 1 0 0 0 0 0 0\n 0.25 2 0 0 0 0 0 0\n 0.333333333333333 6 0 0 0 0 0 0\n 0.416666666666667 1 0 0 0 0 0 0\n 0.5 6 0 0 0 0 0 0\n 0.583333333333333 3 0 0 0 0 0 0\n 0.666666666666667 3 0 0 0 0 0 0\n 0.75 3 0 0 0 0 0 0\n 0.833333333333333 1 0 0 0 0 0 0\n 0.916666666666667 1 0 0 0 0 0 0\n 1 275 0 0 0 0 0 0\n 1.5 2 0 0 0 0 0 0\n 2 308 0 0 0 0 0 0\n 3 246 0 0 0 0 0 0\n 4 233 0 0 0 0 0 0\n 5 242 0 0 0 0 0 0\n 6 0 241 0 0 0 0 0\n 7 0 256 0 0 0 0 0\n 8 0 239 0 0 0 0 0\n 9 0 245 0 0 0 0 0\n 10 0 214 0 0 0 0 0\n 11 0 0 220 0 0 0 0\n 12 0 0 224 0 0 0 0\n 13 0 0 191 0 0 0 0\n 14 0 0 199 0 0 0 0\n 15 0 0 206 0 0 0 0\n 16 0 0 0 186 0 0 0\n 17 0 0 0 164 0 0 0\n 18 0 0 0 141 0 0 0\n 19 0 0 0 130 0 0 0\n 20 0 0 0 149 0 0 0\n 21 0 0 0 0 158 0 0\n 22 0 0 0 0 149 0 0\n 23 0 0 0 0 125 0 0\n 24 0 0 0 0 144 0 0\n 25 0 0 0 0 107 0 0\n 26 0 0 0 0 100 0 0\n 27 0 0 0 0 117 0 0\n 28 0 0 0 0 85 0 0\n 29 0 0 0 0 82 0 0\n 30 0 0 0 0 82 0 0\n 31 0 0 0 0 0 68 0\n 32 0 0 0 0 0 84 0\n 33 0 0 0 0 0 78 0\n 34 0 0 0 0 0 58 0\n 35 0 0 0 0 0 58 0\n 36 0 0 0 0 0 33 0\n 37 0 0 0 0 0 46 0\n 38 0 0 0 0 0 45 0\n 39 0 0 0 0 0 45 0\n 40 0 0 0 0 0 32 0\n 41 0 0 0 0 0 34 0\n 42 0 0 0 0 0 26 0\n 43 0 0 0 0 0 31 0\n 44 0 0 0 0 0 24 0\n 45 0 0 0 0 0 27 0\n 46 0 0 0 0 0 25 0\n 47 0 0 0 0 0 16 0\n 48 0 0 0 0 0 21 0\n 49 0 0 0 0 0 15 0\n 50 0 0 0 0 0 12 0\n 51 0 0 0 0 0 0 13\n 52 0 0 0 0 0 0 7\n 53 0 0 0 0 0 0 4\n 54 0 0 0 0 0 0 6\n 55 0 0 0 0 0 0 9\n 56 0 0 0 0 0 0 7\n 57 0 0 0 0 0 0 9\n 58 0 0 0 0 0 0 6\n 59 0 0 0 0 0 0 5\n 60 0 0 0 0 0 0 4\n 61 0 0 0 0 0 0 2\n 62 0 0 0 0 0 0 1\n 63 0 0 0 0 0 0 5\n 64 0 0 0 0 0 0 1\n 65 0 0 0 0 0 0 5\n 66 0 0 0 0 0 0 3\n 67 0 0 0 0 0 0 2\n 68 0 0 0 0 0 0 1\n 69 0 0 0 0 0 0 3\n 70 0 0 0 0 0 0 1\n 72 0 0 0 0 0 0 0\n 73 0 0 0 0 0 0 0\n 76 0 0 0 0 0 0 0\n 84 0 0 0 0 0 0 0\n <NA> 0 0 0 0 0 0 0\n Categories\nNumeric Values (70,100] <NA>\n 0 0 0\n 0.0833333333333333 0 0\n 0.25 0 0\n 0.333333333333333 0 0\n 0.416666666666667 0 0\n 0.5 0 0\n 0.583333333333333 0 0\n 0.666666666666667 0 0\n 0.75 0 0\n 0.833333333333333 0 0\n 0.916666666666667 0 0\n 1 0 0\n 1.5 0 0\n 2 0 0\n 3 0 0\n 4 0 0\n 5 0 0\n 6 0 0\n 7 0 0\n 8 0 0\n 9 0 0\n 10 0 0\n 11 0 0\n 12 0 0\n 13 0 0\n 14 0 0\n 15 0 0\n 16 0 0\n 17 0 0\n 18 0 0\n 19 0 0\n 20 0 0\n 21 0 0\n 22 0 0\n 23 0 0\n 24 0 0\n 25 0 0\n 26 0 0\n 27 0 0\n 28 0 0\n 29 0 0\n 30 0 0\n 31 0 0\n 32 0 0\n 33 0 0\n 34 0 0\n 35 0 0\n 36 0 0\n 37 0 0\n 38 0 0\n 39 0 0\n 40 0 0\n 41 0 0\n 42 0 0\n 43 0 0\n 44 0 0\n 45 0 0\n 46 0 0\n 47 0 0\n 48 0 0\n 49 0 0\n 50 0 0\n 51 0 0\n 52 0 0\n 53 0 0\n 54 0 0\n 55 0 0\n 56 0 0\n 57 0 0\n 58 0 0\n 59 0 0\n 60 0 0\n 61 0 0\n 62 0 0\n 63 0 0\n 64 0 0\n 65 0 0\n 66 0 0\n 67 0 0\n 68 0 0\n 69 0 0\n 70 0 0\n 72 1 0\n 73 3 0\n 76 1 0\n 84 1 0\n <NA> 0 107\n\n\nRe-labeling NA values\nYou may want to assign NA values a label such as “Missing”. Because the new column is class Factor (restricted values), you cannot simply mutate it with replace_na(), as this value will be rejected. Instead, use fct_na_value_to_level() from forcats as explained in the Factors page.\n\nlinelist <- linelist %>% \n \n # cut() creates age_cat, automatically of class Factor \n mutate(age_cat = cut(\n age_years,\n breaks = c(0, 5, 10, 15, 20, 30, 50, 70, 100), \n right = FALSE,\n include.lowest = TRUE, \n labels = c(\"0-4\", \"5-9\", \"10-14\", \"15-19\", \"20-29\", \"30-49\", \"50-69\", \"70-100\")),\n \n # make missing values explicit\n age_cat = fct_na_value_to_level(\n age_cat,\n level = \"Missing age\") # you can specify the label\n ) \n\n# table to view counts\ntable(linelist$age_cat, useNA = \"always\")\n\n\n 0-4 5-9 10-14 15-19 20-29 30-49 \n 1227 1223 1048 827 1216 848 \n 50-69 70-100 Missing age <NA> \n 105 7 107 0 \n\n\nQuickly make breaks and labels\nFor a fast way to make breaks and label vectors, use something like below. See the R basics page for references on seq() and rep().\n\n# Make break points from 0 to 90 by 5\nage_seq = seq(from = 0, to = 90, by = 5)\nage_seq\n\n# Make labels for the above categories, assuming default cut() settings\nage_labels = paste0(age_seq + 1, \"-\", age_seq + 5)\nage_labels\n\n# check that both vectors are the same length\nlength(age_seq) == length(age_labels)\n\nRead more about cut() in its Help page by entering ?cut in the R console.\n\n\nQuantile breaks\nIn common understanding, “quantiles” or “percentiles” typically refer to a value below which a proportion of values fall. For example, the 95th percentile of ages in linelist would be the age below which 95% of the age fall.\nHowever in common speech, “quartiles” and “deciles” can also refer to the groups of data as equally divided into 4, or 10 groups (note there will be one more break point than group).\nTo get quantile break points, you can use quantile() from the stats package from base R. You provide a numeric vector (e.g. a column in a dataset) and vector of numeric probability values ranging from 0 to 1.0. The break points are returned as a numeric vector. Explore the details of the statistical methodologies by entering ?quantile.\n\nIf your input numeric vector has any missing values it is best to set na.rm = TRUE\n\nSet names = FALSE to get an un-named numeric vector\n\n\nquantile(linelist$age_years, # specify numeric vector to work on\n probs = c(0, .25, .50, .75, .90, .95), # specify the percentiles you want\n na.rm = TRUE) # ignore missing values \n\n 0% 25% 50% 75% 90% 95% \n 0 6 13 23 33 41 \n\n\nYou can use the results of quantile() as break points in age_categories() or cut(). Below we create a new column deciles using cut() where the breaks are defined using quantiles() on age_years. Below, we display the results using tabyl() from janitor so you can see the percentages (see the Descriptive tables page). Note how they are not exactly 10% in each group.\n\nlinelist %>% # begin with linelist\n mutate(deciles = cut(age_years, # create new column decile as cut() on column age_years\n breaks = quantile( # define cut breaks using quantile()\n age_years, # operate on age_years\n probs = seq(0, 1, by = 0.1), # 0.0 to 1.0 by 0.1\n na.rm = TRUE), # ignore missing values\n include.lowest = TRUE)) %>% # for cut() include age 0\n janitor::tabyl(deciles) # pipe to table to display\n\n deciles n percent valid_percent\n [0,2] 748 0.11319613 0.11505922\n (2,5] 721 0.10911017 0.11090601\n (5,7] 497 0.07521186 0.07644978\n (7,10] 698 0.10562954 0.10736810\n (10,13] 635 0.09609564 0.09767728\n (13,17] 755 0.11425545 0.11613598\n (17,21] 578 0.08746973 0.08890940\n (21,26] 625 0.09458232 0.09613906\n (26,33] 596 0.09019370 0.09167820\n (33,84] 648 0.09806295 0.09967697\n <NA> 107 0.01619249 NA\n\n\n\n\nEvenly-sized groups\nAnother tool to make numeric groups is the the dplyr function ntile(), which attempts to break your data into n evenly-sized groups - but be aware that unlike with quantile() the same value could appear in more than one group. Provide the numeric vector and then the number of groups. The values in the new column created is just group “numbers” (e.g. 1 to 10), not the range of values themselves as when using cut().\n\n# make groups with ntile()\nntile_data <- linelist %>% \n mutate(even_groups = ntile(age_years, 10))\n\n# make table of counts and proportions by group\nntile_table <- ntile_data %>% \n janitor::tabyl(even_groups)\n \n# attach min/max values to demonstrate ranges\nntile_ranges <- ntile_data %>% \n group_by(even_groups) %>% \n summarise(\n min = min(age_years, na.rm=T),\n max = max(age_years, na.rm=T)\n )\n\nWarning: There were 2 warnings in `summarise()`.\nThe first warning was:\nℹ In argument: `min = min(age_years, na.rm = T)`.\nℹ In group 11: `even_groups = NA`.\nCaused by warning in `min()`:\n! no non-missing arguments to min; returning Inf\nℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.\n\n# combine and print - note that values are present in multiple groups\nleft_join(ntile_table, ntile_ranges, by = \"even_groups\")\n\n even_groups n percent valid_percent min max\n 1 651 0.09851695 0.10013844 0 2\n 2 650 0.09836562 0.09998462 2 5\n 3 650 0.09836562 0.09998462 5 7\n 4 650 0.09836562 0.09998462 7 10\n 5 650 0.09836562 0.09998462 10 13\n 6 650 0.09836562 0.09998462 13 17\n 7 650 0.09836562 0.09998462 17 21\n 8 650 0.09836562 0.09998462 21 26\n 9 650 0.09836562 0.09998462 26 33\n 10 650 0.09836562 0.09998462 33 84\n NA 107 0.01619249 NA Inf -Inf\n\n\n\n\n\ncase_when()\nIt is possible to use the dplyr function case_when() to create categories from a numeric column, but it is easier to use age_categories() from epikit or cut() because these will create an ordered factor automatically.\nIf using case_when(), please review the proper use as described earlier in the Re-code values section of this page. Also be aware that all right-hand side values must be of the same class. Thus, if you want NA on the right-side you should either write “Missing” or use the special NA value NA_character_.\n\n\nAdd to pipe chain\nBelow, code to create two categorical age columns is added to the cleaning pipe chain:\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n\n # add column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n\n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age)) %>% \n \n # add column: delay to hospitalisation\n mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% \n \n # clean values of hospital column\n mutate(hospital = recode(hospital,\n # OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n )) %>% \n \n mutate(hospital = replace_na(hospital, \"Missing\")) %>% \n\n # create age_years column (from age and age_unit)\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age,\n age_unit == \"months\" ~ age/12,\n is.na(age_unit) ~ age)) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ################################################### \n mutate(\n # age categories: custom\n age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),\n \n # age categories: 0 to 85 by 5s\n age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5)))", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -714,7 +714,7 @@ "href": "new_pages/cleaning.html#filter-rows", "title": "8  Cleaning data and core functions", "section": "8.11 Filter rows", - "text": "8.11 Filter rows\nA typical cleaning step after you have cleaned the columns and re-coded values is to filter the data frame for specific rows using the dplyr verb filter().\nWithin filter(), specify the logic that must be TRUE for a row in the dataset to be kept. Below we show how to filter rows based on simple and complex logical conditions.\n\n\nSimple filter\nThis simple example re-defines the dataframe linelist as itself, having filtered the rows to meet a logical condition. Only the rows where the logical statement within the parentheses evaluates to TRUE are kept.\nIn this example, the logical statement is gender == \"f\", which is asking whether the value in the column gender is equal to “f” (case sensitive).\nBefore the filter is applied, the number of rows in linelist is nrow(linelist).\n\nlinelist <- linelist %>% \n filter(gender == \"f\") # keep only rows where gender is equal to \"f\"\n\nAfter the filter is applied, the number of rows in linelist is linelist %>% filter(gender == \"f\") %>% nrow().\n\n\nFilter out missing values\nIt is fairly common to want to filter out rows that have missing values. Resist the urge to write filter(!is.na(column) & !is.na(column)) and instead use the tidyr function that is custom-built for this purpose: drop_na(). If run with empty parentheses, it removes rows with any missing values. Alternatively, you can provide names of specific columns to be evaluated for missingness, or use the “tidyselect” helper functions described above.\n\nlinelist %>% \n drop_na(case_id, age_years) # drop rows with missing values for case_id or age_years\n\nSee the page on Missing data for many techniques to analyse and manage missingness in your data.\n\n\nFilter by row number\nIn a data frame or tibble, each row will usually have a “row number” that (when seen in R Viewer) appears to the left of the first column. It is not itself a true column in the data, but it can be used in a filter() statement.\nTo filter based on “row number”, you can use the dplyr function row_number() with open parentheses as part of a logical filtering statement. Often you will use the %in% operator and a range of numbers as part of that logical statement, as shown below. To see the first N rows, you can also use the special dplyr function head().\n\n# View first 100 rows\nlinelist %>% head(100) # or use tail() to see the n last rows\n\n# Show row 5 only\nlinelist %>% filter(row_number() == 5)\n\n# View rows 2 through 20, and three specific columns\nlinelist %>% filter(row_number() %in% 2:20) %>% select(date_onset, outcome, age)\n\nYou can also convert the row numbers to a true column by piping your data frame to the tibble function rownames_to_column() (do not put anything in the parentheses).\n\n\n\nComplex filter\nMore complex logical statements can be constructed using parentheses ( ), OR |, negate !, %in%, and AND & operators. An example is below:\nNote: You can use the ! operator in front of a logical criteria to negate it. For example, !is.na(column) evaluates to true if the column value is not missing. Likewise !column %in% c(\"a\", \"b\", \"c\") evaluates to true if the column value is not in the vector.\n\nExamine the data\nBelow is a simple one-line command to create a histogram of onset dates. See that a second smaller outbreak from 2012-2013 is also included in this raw dataset. For our analyses, we want to remove entries from this earlier outbreak.\n\nhist(linelist$date_onset, breaks = 50)\n\n\n\n\n\n\n\n\n\n\nHow filters handle missing numeric and date values\nCan we just filter by date_onset to rows after June 2013? Caution! Applying the code filter(date_onset > as.Date(\"2013-06-01\"))) would remove any rows in the later epidemic with a missing date of onset!\nDANGER: Filtering to greater than (>) or less than (<) a date or number can remove any rows with missing values (NA)! This is because NA is treated as infinitely large and small.\n(See the page on Working with dates for more information on working with dates and the package lubridate)\n\n\nDesign the filter\nExamine a cross-tabulation to make sure we exclude only the correct rows:\n\ntable(Hospital = linelist$hospital, # hospital name\n YearOnset = lubridate::year(linelist$date_onset), # year of date_onset\n useNA = \"always\") # show missing values\n\n YearOnset\nHospital 2012 2013 2014 2015 <NA>\n Central Hospital 0 0 351 99 18\n Hospital A 229 46 0 0 15\n Hospital B 227 47 0 0 15\n Military Hospital 0 0 676 200 34\n Missing 0 0 1117 318 77\n Other 0 0 684 177 46\n Port Hospital 9 1 1372 347 75\n St. Mark's Maternity Hospital (SMMH) 0 0 322 93 13\n <NA> 0 0 0 0 0\n\n\nWhat other criteria can we filter on to remove the first outbreak (in 2012 & 2013) from the dataset? We see that:\n\nThe first epidemic in 2012 & 2013 occurred at Hospital A, Hospital B, and that there were also 10 cases at Port Hospital.\n\nHospitals A & B did not have cases in the second epidemic, but Port Hospital did.\n\nWe want to exclude:\n\nThe nrow(linelist %>% filter(hospital %in% c(\"Hospital A\", \"Hospital B\") | date_onset < as.Date(\"2013-06-01\"))) rows with onset in 2012 and 2013 at either hospital A, B, or Port:\n\nExclude nrow(linelist %>% filter(date_onset < as.Date(\"2013-06-01\"))) rows with onset in 2012 and 2013\nExclude nrow(linelist %>% filter(hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset))) rows from Hospitals A & B with missing onset dates\n\nDo not exclude nrow(linelist %>% filter(!hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset))) other rows with missing onset dates.\n\n\nWe start with a linelist of nrow(linelist)`. Here is our filter statement:\n\nlinelist <- linelist %>% \n # keep rows where onset is after 1 June 2013 OR where onset is missing and it was a hospital OTHER than Hospital A or B\n filter(date_onset > as.Date(\"2013-06-01\") | (is.na(date_onset) & !hospital %in% c(\"Hospital A\", \"Hospital B\")))\n\nnrow(linelist)\n\n[1] 6019\n\n\nWhen we re-make the cross-tabulation, we see that Hospitals A & B are removed completely, and the 10 Port Hospital cases from 2012 & 2013 are removed, and all other values are the same - just as we wanted.\n\ntable(Hospital = linelist$hospital, # hospital name\n YearOnset = lubridate::year(linelist$date_onset), # year of date_onset\n useNA = \"always\") # show missing values\n\n YearOnset\nHospital 2014 2015 <NA>\n Central Hospital 351 99 18\n Military Hospital 676 200 34\n Missing 1117 318 77\n Other 684 177 46\n Port Hospital 1372 347 75\n St. Mark's Maternity Hospital (SMMH) 322 93 13\n <NA> 0 0 0\n\n\nMultiple statements can be included within one filter command (separated by commas), or you can always pipe to a separate filter() command for clarity.\nNote: some readers may notice that it would be easier to just filter by date_hospitalisation because it is 100% complete with no missing values. This is true. But date_onset is used for purposes of demonstrating a complex filter.\n\n\n\nStandalone\nFiltering can also be done as a stand-alone command (not part of a pipe chain). Like other dplyr verbs, in this case the first argument must be the dataset itself.\n\n# dataframe <- filter(dataframe, condition(s) for rows to keep)\n\nlinelist <- filter(linelist, !is.na(case_id))\n\nYou can also use base R to subset using square brackets which reflect the [rows, columns] that you want to retain.\n\n# dataframe <- dataframe[row conditions, column conditions] (blank means keep all)\n\nlinelist <- linelist[!is.na(case_id), ]\n\n\n\nQuickly review records\nOften you want to quickly review a few records, for only a few columns. The base R function View() will print a data frame for viewing in your RStudio.\nView the linelist in RStudio:\n\nView(linelist)\n\nHere are two examples of viewing specific cells (specific rows, and specific columns):\nWith dplyr functions filter() and select():\nWithin View(), pipe the dataset to filter() to keep certain rows, and then to select() to keep certain columns. For example, to review onset and hospitalization dates of 3 specific cases:\n\nView(linelist %>%\n filter(case_id %in% c(\"11f8ea\", \"76b97a\", \"47a5f5\")) %>%\n select(date_onset, date_hospitalisation))\n\nYou can achieve the same with base R syntax, using brackets [ ] to subset you want to see.\n\nView(linelist[linelist$case_id %in% c(\"11f8ea\", \"76b97a\", \"47a5f5\"), c(\"date_onset\", \"date_hospitalisation\")])\n\n\nAdd to pipe chain\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n\n # add column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n\n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age)) %>% \n \n # add column: delay to hospitalisation\n mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% \n \n # clean values of hospital column\n mutate(hospital = recode(hospital,\n # OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n )) %>% \n \n mutate(hospital = replace_na(hospital, \"Missing\")) %>% \n\n # create age_years column (from age and age_unit)\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age,\n age_unit == \"months\" ~ age/12,\n is.na(age_unit) ~ age)) %>% \n \n mutate(\n # age categories: custom\n age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),\n \n # age categories: 0 to 85 by 5s\n age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5))) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ###################################################\n filter(\n # keep only rows where case_id is not missing\n !is.na(case_id), \n \n # also filter to keep only the second outbreak\n date_onset > as.Date(\"2013-06-01\") | (is.na(date_onset) & !hospital %in% c(\"Hospital A\", \"Hospital B\")))", + "text": "8.11 Filter rows\nA typical cleaning step after you have cleaned the columns and re-coded values is to filter the data frame for specific rows using the dplyr verb filter().\nWithin filter(), specify the logic that must be TRUE for a row in the dataset to be kept. Below we show how to filter rows based on simple and complex logical conditions.\n\n\nSimple filter\nThis simple example re-defines the dataframe linelist as itself, having filtered the rows to meet a logical condition. Only the rows where the logical statement within the parentheses evaluates to TRUE are kept.\nIn this example, the logical statement is gender == \"f\", which is asking whether the value in the column gender is equal to “f” (case sensitive).\nBefore the filter is applied, the number of rows in linelist is nrow(linelist).\n\nlinelist <- linelist %>% \n filter(gender == \"f\") # keep only rows where gender is equal to \"f\"\n\nAfter the filter is applied, the number of rows in linelist is linelist %>% filter(gender == \"f\") %>% nrow().\n\n\nFilter out missing values\nIt is fairly common to want to filter out rows that have missing values. Resist the urge to write filter(!is.na(column) & !is.na(column)) and instead use the tidyr function that is custom-built for this purpose: drop_na(). If run with empty parentheses, it removes rows with any missing values. Alternatively, you can provide names of specific columns to be evaluated for missingness, or use the “tidyselect” helper functions described above.\n\nlinelist %>% \n drop_na(case_id, age_years) # drop rows with missing values for case_id or age_years\n\nSee the page on Missing data for many techniques to analyse and manage missingness in your data.\n\n\nFilter by row number\nIn a data frame or tibble, each row will usually have a “row number” that (when seen in R Viewer) appears to the left of the first column. It is not itself a true column in the data, but it can be used in a filter() statement.\nTo filter based on “row number”, you can use the dplyr function row_number() with open parentheses as part of a logical filtering statement. Often you will use the %in% operator and a range of numbers as part of that logical statement, as shown below. To see the first N rows, you can also use the special dplyr function head().\n\n# View first 100 rows\nlinelist %>% head(100) # or use tail() to see the n last rows\n\n# Show row 5 only\nlinelist %>% filter(row_number() == 5)\n\n# View rows 2 through 20, and three specific columns\nlinelist %>% filter(row_number() %in% 2:20) %>% select(date_onset, outcome, age)\n\nYou can also convert the row numbers to a true column by piping your data frame to the tibble function rownames_to_column() (do not put anything in the parentheses).\n\n\n\nComplex filter\nMore complex logical statements can be constructed using parentheses ( ), OR |, negate !, %in%, and AND & operators. An example is below:\nNote: You can use the ! operator in front of a logical criteria to negate it. For example, !is.na(column) evaluates to true if the column value is not missing. Likewise !column %in% c(\"a\", \"b\", \"c\") evaluates to true if the column value is not in the vector.\n\nExamine the data\nBelow is a simple one-line command to create a histogram of onset dates. See that a second smaller outbreak from 2012-2013 is also included in this raw dataset. For our analyses, we want to remove entries from this earlier outbreak.\n\nhist(linelist$date_onset, breaks = 50)\n\n\n\n\n\n\n\n\n\n\nHow filters handle missing numeric and date values\nCan we just filter by date_onset to rows after June 2013? Caution! Applying the code filter(date_onset > as.Date(\"2013-06-01\"))) would remove any rows in the later epidemic with a missing date of onset!\nDANGER: Filtering to greater than (>) or less than (<) a date or number can remove any rows with missing values (NA)! This is because NA is treated as infinitely large and small.\n(See the page on Working with dates for more information on working with dates and the package lubridate)\n\n\nDesign the filter\nExamine a cross-tabulation to make sure we exclude only the correct rows:\n\ntable(Hospital = linelist$hospital, # hospital name\n YearOnset = lubridate::year(linelist$date_onset), # year of date_onset\n useNA = \"always\") # show missing values\n\n YearOnset\nHospital 2012 2013 2014 2015 <NA>\n Central Hospital 0 0 351 99 18\n Hospital A 229 46 0 0 15\n Hospital B 227 47 0 0 15\n Military Hospital 0 0 676 200 34\n Missing 0 0 1117 318 77\n Other 0 0 684 177 46\n Port Hospital 9 1 1372 347 75\n St. Mark's Maternity Hospital (SMMH) 0 0 322 93 13\n <NA> 0 0 0 0 0\n\n\nWhat other criteria can we filter on to remove the first outbreak (in 2012 & 2013) from the dataset? We see that:\n\nThe first epidemic in 2012 & 2013 occurred at Hospital A, Hospital B, and that there were also 10 cases at Port Hospital.\n\nHospitals A & B did not have cases in the second epidemic, but Port Hospital did.\n\nWe want to exclude:\n\nThe rows with onset in 2012 and 2013 at either hospital A, B, or Port: nrow(linelist %>% filter(hospital %in% c(\"Hospital A\", \"Hospital B\") | date_onset < as.Date(\"2013-06-01\")))\n\nExclude rows with onset in 2012 and 2013 nrow(linelist %>% filter(date_onset < as.Date(\"2013-06-01\")))\nExclude rows from Hospitals A & B with missing onset dates\nnrow(linelist %>% filter(hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))\nDo not exclude other rows with missing onset dates.\nnrow(linelist %>% filter(!hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))\n\n\nWe start with a linelist of nrow(linelist)`. Here is our filter statement:\n\nlinelist <- linelist %>% \n # keep rows where onset is after 1 June 2013 OR where onset is missing and it was a hospital OTHER than Hospital A or B\n filter(date_onset > as.Date(\"2013-06-01\") | (is.na(date_onset) & !hospital %in% c(\"Hospital A\", \"Hospital B\")))\n\nnrow(linelist)\n\n[1] 6019\n\n\nWhen we re-make the cross-tabulation, we see that Hospitals A & B are removed completely, and the 10 Port Hospital cases from 2012 & 2013 are removed, and all other values are the same - just as we wanted.\n\ntable(Hospital = linelist$hospital, # hospital name\n YearOnset = lubridate::year(linelist$date_onset), # year of date_onset\n useNA = \"always\") # show missing values\n\n YearOnset\nHospital 2014 2015 <NA>\n Central Hospital 351 99 18\n Military Hospital 676 200 34\n Missing 1117 318 77\n Other 684 177 46\n Port Hospital 1372 347 75\n St. Mark's Maternity Hospital (SMMH) 322 93 13\n <NA> 0 0 0\n\n\nMultiple statements can be included within one filter command (separated by commas), or you can always pipe to a separate filter() command for clarity.\nNote: some readers may notice that it would be easier to just filter by date_hospitalisation because it is 100% complete with no missing values. This is true. But date_onset is used for purposes of demonstrating a complex filter.\n\n\n\nStandalone\nFiltering can also be done as a stand-alone command (not part of a pipe chain). Like other dplyr verbs, in this case the first argument must be the dataset itself.\n\n# dataframe <- filter(dataframe, condition(s) for rows to keep)\n\nlinelist <- filter(linelist, !is.na(case_id))\n\nYou can also use base R to subset using square brackets which reflect the [rows, columns] that you want to retain.\n\n# dataframe <- dataframe[row conditions, column conditions] (blank means keep all)\n\nlinelist <- linelist[!is.na(case_id), ]\n\n\n\nQuickly review records\nOften you want to quickly review a few records, for only a few columns. The base R function View() will print a data frame for viewing in your RStudio.\nView the linelist in RStudio:\n\nView(linelist)\n\nHere are two examples of viewing specific cells (specific rows, and specific columns):\nWith dplyr functions filter() and select():\nWithin View(), pipe the dataset to filter() to keep certain rows, and then to select() to keep certain columns. For example, to review onset and hospitalization dates of 3 specific cases:\n\nView(linelist %>%\n filter(case_id %in% c(\"11f8ea\", \"76b97a\", \"47a5f5\")) %>%\n select(date_onset, date_hospitalisation))\n\nYou can achieve the same with base R syntax, using brackets [ ] to subset you want to see.\n\nView(linelist[linelist$case_id %in% c(\"11f8ea\", \"76b97a\", \"47a5f5\"), c(\"date_onset\", \"date_hospitalisation\")])\n\n\nAdd to pipe chain\n\n# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)\n##################################################################################\n\n# begin cleaning pipe chain\n###########################\nlinelist <- linelist_raw %>%\n \n # standardize column name syntax\n janitor::clean_names() %>% \n \n # manually re-name columns\n # NEW name # OLD name\n rename(date_infection = infection_date,\n date_hospitalisation = hosp_date,\n date_outcome = date_of_outcome) %>% \n \n # remove column\n select(-c(row_num, merged_header, x28)) %>% \n \n # de-duplicate\n distinct() %>% \n\n # add column\n mutate(bmi = wt_kg / (ht_cm/100)^2) %>% \n\n # convert class of columns\n mutate(across(contains(\"date\"), as.Date), \n generation = as.numeric(generation),\n age = as.numeric(age)) %>% \n \n # add column: delay to hospitalisation\n mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% \n \n # clean values of hospital column\n mutate(hospital = recode(hospital,\n # OLD = NEW\n \"Mitylira Hopital\" = \"Military Hospital\",\n \"Mitylira Hospital\" = \"Military Hospital\",\n \"Military Hopital\" = \"Military Hospital\",\n \"Port Hopital\" = \"Port Hospital\",\n \"Central Hopital\" = \"Central Hospital\",\n \"other\" = \"Other\",\n \"St. Marks Maternity Hopital (SMMH)\" = \"St. Mark's Maternity Hospital (SMMH)\"\n )) %>% \n \n mutate(hospital = replace_na(hospital, \"Missing\")) %>% \n\n # create age_years column (from age and age_unit)\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age,\n age_unit == \"months\" ~ age/12,\n is.na(age_unit) ~ age)) %>% \n \n mutate(\n # age categories: custom\n age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),\n \n # age categories: 0 to 85 by 5s\n age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5))) %>% \n \n # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED\n ###################################################\n filter(\n # keep only rows where case_id is not missing\n !is.na(case_id), \n \n # also filter to keep only the second outbreak\n date_onset > as.Date(\"2013-06-01\") | (is.na(date_onset) & !hospital %in% c(\"Hospital A\", \"Hospital B\")))", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -725,7 +725,7 @@ "href": "new_pages/cleaning.html#row-wise-calculations", "title": "8  Cleaning data and core functions", "section": "8.12 Row-wise calculations", - "text": "8.12 Row-wise calculations\nIf you want to perform a calculation within a row, you can use rowwise() from dplyr. See this online vignette on row-wise calculations. For example, this code applies rowwise() and then creates a new column that sums the number of the specified symptom columns that have value “yes”, for each row in the linelist. The columns are specified within sum() by name within a vector c(). rowwise() is essentially a special kind of group_by(), so it is best to use ungroup() when you are done (page on Grouping data).\n\nlinelist %>%\n rowwise() %>%\n mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == \"yes\")) %>% \n ungroup() %>% \n select(fever, chills, cough, aches, vomit, num_symptoms) # for display\n\n# A tibble: 5,888 × 6\n fever chills cough aches vomit num_symptoms\n <chr> <chr> <chr> <chr> <chr> <int>\n 1 no no yes no yes 2\n 2 <NA> <NA> <NA> <NA> <NA> NA\n 3 <NA> <NA> <NA> <NA> <NA> NA\n 4 no no no no no 0\n 5 no no yes no yes 2\n 6 no no yes no yes 2\n 7 <NA> <NA> <NA> <NA> <NA> NA\n 8 no no yes no yes 2\n 9 no no yes no yes 2\n10 no no yes no no 1\n# ℹ 5,878 more rows\n\n\nAs you specify the column to evaluate, you may want to use the “tidyselect” helper functions described in the select() section of this page. You just have to make one adjustment (because you are not using them within a dplyr function like select() or summarise()).\nPut the column-specification criteria within the dplyr function c_across(). This is because c_across (documentation) is designed to work with rowwise() specifically. For example, the following code:\n\nApplies rowwise() so the following operation (sum()) is applied within each row (not summing entire columns)\n\nCreates new column num_NA_dates, defined for each row as the number of columns (with name containing “date”) for which is.na() evaluated to TRUE (they are missing data).\n\nungroup() to remove the effects of rowwise() for subsequent steps\n\n\nlinelist %>%\n rowwise() %>%\n mutate(num_NA_dates = sum(is.na(c_across(contains(\"date\"))))) %>% \n ungroup() %>% \n select(num_NA_dates, contains(\"date\")) # for display\n\n# A tibble: 5,888 × 5\n num_NA_dates date_infection date_onset date_hospitalisation date_outcome\n <int> <date> <date> <date> <date> \n 1 1 2014-05-08 2014-05-13 2014-05-15 NA \n 2 1 NA 2014-05-13 2014-05-14 2014-05-18 \n 3 1 NA 2014-05-16 2014-05-18 2014-05-30 \n 4 1 2014-05-04 2014-05-18 2014-05-20 NA \n 5 0 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 6 0 2014-05-03 2014-05-22 2014-05-23 2014-05-24 \n 7 0 2014-05-22 2014-05-27 2014-05-29 2014-06-01 \n 8 0 2014-05-28 2014-06-02 2014-06-03 2014-06-07 \n 9 1 NA 2014-06-05 2014-06-06 2014-06-18 \n10 1 NA 2014-06-05 2014-06-07 2014-06-09 \n# ℹ 5,878 more rows\n\n\nYou could also provide other functions, such as max() to get the latest or most recent date for each row:\n\nlinelist %>%\n rowwise() %>%\n mutate(latest_date = max(c_across(contains(\"date\")), na.rm=T)) %>% \n ungroup() %>% \n select(latest_date, contains(\"date\")) # for display\n\n# A tibble: 5,888 × 5\n latest_date date_infection date_onset date_hospitalisation date_outcome\n <date> <date> <date> <date> <date> \n 1 2014-05-15 2014-05-08 2014-05-13 2014-05-15 NA \n 2 2014-05-18 NA 2014-05-13 2014-05-14 2014-05-18 \n 3 2014-05-30 NA 2014-05-16 2014-05-18 2014-05-30 \n 4 2014-05-20 2014-05-04 2014-05-18 2014-05-20 NA \n 5 2014-05-29 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 6 2014-05-24 2014-05-03 2014-05-22 2014-05-23 2014-05-24 \n 7 2014-06-01 2014-05-22 2014-05-27 2014-05-29 2014-06-01 \n 8 2014-06-07 2014-05-28 2014-06-02 2014-06-03 2014-06-07 \n 9 2014-06-18 NA 2014-06-05 2014-06-06 2014-06-18 \n10 2014-06-09 NA 2014-06-05 2014-06-07 2014-06-09 \n# ℹ 5,878 more rows", + "text": "8.12 Row-wise calculations\nIf you want to perform a calculation within a row, you can use rowwise() from dplyr. See this online vignette on row-wise calculations. For example, this code applies rowwise() and then creates a new column that sums the number of the specified symptom columns that have value “yes”, for each row in the linelist. The columns are specified within sum() by name within a vector c(). rowwise() is essentially a special kind of group_by(), so it is best to use ungroup() when you are done (page on Grouping data).\n\nlinelist %>%\n rowwise() %>%\n mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == \"yes\"), na.rm =T) %>% \n ungroup() %>% \n select(fever, chills, cough, aches, vomit, num_symptoms) # for display\n\n# A tibble: 5,888 × 6\n fever chills cough aches vomit num_symptoms\n <chr> <chr> <chr> <chr> <chr> <int>\n 1 no no yes no yes 2\n 2 <NA> <NA> <NA> <NA> <NA> NA\n 3 <NA> <NA> <NA> <NA> <NA> NA\n 4 no no no no no 0\n 5 no no yes no yes 2\n 6 no no yes no yes 2\n 7 <NA> <NA> <NA> <NA> <NA> NA\n 8 no no yes no yes 2\n 9 no no yes no yes 2\n10 no no yes no no 1\n# ℹ 5,878 more rows\n\n\nAs you specify the column to evaluate, you may want to use the “tidyselect” helper functions described in the select() section of this page. You just have to make one adjustment (because you are not using them within a dplyr function like select() or summarise()).\nPut the column-specification criteria within the dplyr function c_across(). This is because c_across (documentation) is designed to work with rowwise() specifically. For example, the following code:\n\nApplies rowwise() so the following operation (sum()) is applied within each row (not summing entire columns).\n\nCreates new column num_NA_dates, defined for each row as the number of columns (with name containing “date”) for which is.na() evaluated to TRUE (they are missing data).\n\nungroup() to remove the effects of rowwise() for subsequent steps.\n\n\nlinelist %>%\n rowwise() %>%\n mutate(num_NA_dates = sum(is.na(c_across(contains(\"date\"))))) %>% \n ungroup() %>% \n select(num_NA_dates, contains(\"date\")) # for display\n\n# A tibble: 5,888 × 5\n num_NA_dates date_infection date_onset date_hospitalisation date_outcome\n <int> <date> <date> <date> <date> \n 1 1 2014-05-08 2014-05-13 2014-05-15 NA \n 2 1 NA 2014-05-13 2014-05-14 2014-05-18 \n 3 1 NA 2014-05-16 2014-05-18 2014-05-30 \n 4 1 2014-05-04 2014-05-18 2014-05-20 NA \n 5 0 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 6 0 2014-05-03 2014-05-22 2014-05-23 2014-05-24 \n 7 0 2014-05-22 2014-05-27 2014-05-29 2014-06-01 \n 8 0 2014-05-28 2014-06-02 2014-06-03 2014-06-07 \n 9 1 NA 2014-06-05 2014-06-06 2014-06-18 \n10 1 NA 2014-06-05 2014-06-07 2014-06-09 \n# ℹ 5,878 more rows\n\n\nYou could also provide other functions, such as max() to get the latest or most recent date for each row:\n\nlinelist %>%\n rowwise() %>%\n mutate(latest_date = max(c_across(contains(\"date\")), na.rm=T)) %>% \n ungroup() %>% \n select(latest_date, contains(\"date\")) # for display\n\n# A tibble: 5,888 × 5\n latest_date date_infection date_onset date_hospitalisation date_outcome\n <date> <date> <date> <date> <date> \n 1 2014-05-15 2014-05-08 2014-05-13 2014-05-15 NA \n 2 2014-05-18 NA 2014-05-13 2014-05-14 2014-05-18 \n 3 2014-05-30 NA 2014-05-16 2014-05-18 2014-05-30 \n 4 2014-05-20 2014-05-04 2014-05-18 2014-05-20 NA \n 5 2014-05-29 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 6 2014-05-24 2014-05-03 2014-05-22 2014-05-23 2014-05-24 \n 7 2014-06-01 2014-05-22 2014-05-27 2014-05-29 2014-06-01 \n 8 2014-06-07 2014-05-28 2014-06-02 2014-06-03 2014-06-07 \n 9 2014-06-18 NA 2014-06-05 2014-06-06 2014-06-18 \n10 2014-06-09 NA 2014-06-05 2014-06-07 2014-06-09 \n# ℹ 5,878 more rows", "crumbs": [ "Data Management", "8  Cleaning data and core functions" @@ -758,7 +758,7 @@ "href": "new_pages/dates.html#preparation", "title": "9  Working with dates", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for this page. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\n# Checks if package is installed, installs if necessary, and loads package for current session\n\npacman::p_load(\n lubridate, # general package for handling and converting dates \n parsedate, # has function to \"guess\" messy dates\n aweek, # another option for converting dates to weeks, and weeks to dates\n zoo, # additional date/time functions\n here, # file management\n rio, # data import/export\n tidyverse) # data management and visualization \n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow along step-by-step, see instruction in the Download handbook and data page. We assume the file is in the working directory so no sub-folders are specified in this file path.\n\nlinelist <- import(\"linelist_cleaned.xlsx\")", + "text": "Load packages\nThis code chunk shows the loading of packages required for this page. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\n# Checks if package is installed, installs if necessary, and loads package for current session\n\npacman::p_load(\n lubridate, # general package for handling and converting dates \n parsedate, # has function to \"guess\" messy dates\n aweek, # another option for converting dates to weeks, and weeks to dates\n zoo, # additional date/time functions\n here, # file management\n rio, # data import/export\n tidyverse # data management and visualization\n ) \n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow along step-by-step, see instruction in the Download handbook and data page. We assume the file is in the working directory so no sub-folders are specified in this file path.\n\nlinelist <- import(\"linelist_cleaned.xlsx\")", "crumbs": [ "Data Management", "9  Working with dates" @@ -769,7 +769,7 @@ "href": "new_pages/dates.html#current-date", "title": "9  Working with dates", "section": "9.2 Current date", - "text": "9.2 Current date\nYou can get the current “system” date or system datetime of your computer by doing the following with base R.\n\n# get the system date - this is a DATE class\nSys.Date()\n\n[1] \"2024-06-19\"\n\n# get the system time - this is a DATETIME class\nSys.time()\n\n[1] \"2024-06-19 12:27:19 CEST\"\n\n\nWith the lubridate package these can also be returned with today() and now(), respectively. date() returns the current date and time with weekday and month names.", + "text": "9.2 Current date\nYou can get the current “system” date or system datetime of your computer by doing the following with base R.\n\n# get the system date - this is a DATE class\nSys.Date()\n\n[1] \"2024-10-14\"\n\n# get the system time - this is a DATETIME class\nSys.time()\n\n[1] \"2024-10-14 17:00:53 PDT\"\n\n\nWith the lubridate package these can also be returned with today() and now(), respectively. date() returns the current date and time with weekday and month names.", "crumbs": [ "Data Management", "9  Working with dates" @@ -780,7 +780,7 @@ "href": "new_pages/dates.html#convert-to-date", "title": "9  Working with dates", "section": "9.3 Convert to Date", - "text": "9.3 Convert to Date\nAfter importing a dataset into R, date column values may look like “1989/12/30”, “05/06/2014”, or “13 Jan 2020”. In these cases, R is likely still treating these values as Character values. R must be told that these values are dates… and what the format of the date is (which part is Day, which is Month, which is Year, etc).\nOnce told, R converts these values to class Date. In the background, R will store the dates as numbers (the number of days from its “origin” date 1 Jan 1970). You will not interface with the date number often, but this allows for R to treat dates as continuous variables and to allow special operations such as calculating the distance between dates.\nBy default, values of class Date in R are displayed as YYYY-MM-DD. Later in this section we will discuss how to change the display of date values.\nBelow we present two approaches to converting a column from character values to class Date.\nTIP: You can check the current class of a column with base R function class(), like class(linelist$date_onset).\n\nbase R\nas.Date() is the standard, base R function to convert an object or column to class Date (note capitalization of “D”).\nUse of as.Date() requires that:\n\nYou specify the existing format of the raw character date or the origin date if supplying dates as numbers (see section on Excel dates)\n\nIf used on a character column, all date values must have the same exact format (if this is not the case, try parse_date() from the parsedate package)\n\nFirst, check the class of your column with class() from base R. If you are unsure or confused about the class of your data (e.g. you see “POSIXct”, etc.) it can be easiest to first convert the column to class Character with as.character(), and then convert it to class Date.\nSecond, within the as.Date() function, use the format = argument to tell R the current format of the character date components - which characters refer to the month, the day, and the year, and how they are separated. If your values are already in one of R’s standard date formats (“YYYY-MM-DD” or “YYYY/MM/DD”) the format = argument is not necessary.\nTo format =, provide a character string (in quotes) that represents the current date format using the special “strptime” abbreviations below. For example, if your character dates are currently in the format “DD/MM/YYYY”, like “24/04/1968”, then you would use format = \"%d/%m/%Y\" to convert the values into dates. Putting the format in quotation marks is necessary. And don’t forget any slashes or dashes!\n\n# Convert to class date\nlinelist <- linelist %>% \n mutate(date_onset = as.Date(date_of_onset, format = \"%d/%m/%Y\"))\n\nMost of the strptime abbreviations are listed below. You can see the complete list by running ?strptime.\n%d = Day number of month (5, 17, 28, etc.)\n%j = Day number of the year (Julian day 001-366)\n%a = Abbreviated weekday (Mon, Tue, Wed, etc.)\n%A = Full weekday (Monday, Tuesday, etc.) %w = Weekday number (0-6, Sunday is 0)\n%u = Weekday number (1-7, Monday is 1)\n%W = Week number (00-53, Monday is week start)\n%U = Week number (01-53, Sunday is week start)\n%m = Month number (e.g. 01, 02, 03, 04)\n%b = Abbreviated month (Jan, Feb, etc.)\n%B = Full month (January, February, etc.)\n%y = 2-digit year (e.g. 89)\n%Y = 4-digit year (e.g. 1989)\n%h = hours (24-hr clock)\n%m = minutes\n%s = seconds %z = offset from GMT\n%Z = Time zone (character)\nTIP: The format = argument of as.Date() is not telling R the format you want the dates to be, but rather how to identify the date parts as they are before you run the command.\nTIP: Be sure that in the format = argument you use the date-part separator (e.g. /, -, or space) that is present in your dates.\nOnce the values are in class Date, R will by default display them in the standard format, which is YYYY-MM-DD.\n\n\nlubridate\nConverting character objects to dates can be made easier by using the lubridate package. This is a tidyverse package designed to make working with dates and times more simple and consistent than in base R. For these reasons, lubridate is often considered the gold-standard package for dates and time, and is recommended whenever working with them.\nThe lubridate package provides several different helper functions designed to convert character objects to dates in an intuitive, and more lenient way than specifying the format in as.Date(). These functions are specific to the rough date format, but allow for a variety of separators, and synonyms for dates (e.g. 01 vs Jan vs January) - they are named after abbreviations of date formats.\n\n# install/load lubridate \npacman::p_load(lubridate)\n\nThe ymd() function flexibly converts date values supplied as year, then month, then day.\n\n# read date in year-month-day format\nymd(\"2020-10-11\")\n\n[1] \"2020-10-11\"\n\nymd(\"20201011\")\n\n[1] \"2020-10-11\"\n\n\nThe mdy() function flexibly converts date values supplied as month, then day, then year.\n\n# read date in month-day-year format\nmdy(\"10/11/2020\")\n\n[1] \"2020-10-11\"\n\nmdy(\"Oct 11 20\")\n\n[1] \"2020-10-11\"\n\n\nThe dmy() function flexibly converts date values supplied as day, then month, then year.\n\n# read date in day-month-year format\ndmy(\"11 10 2020\")\n\n[1] \"2020-10-11\"\n\ndmy(\"11 October 2020\")\n\n[1] \"2020-10-11\"\n\n\n\n\n\n\nIf using piping, the conversion of a character column to dates with lubridate might look like this:\n\nlinelist <- linelist %>%\n mutate(date_onset = lubridate::dmy(date_onset))\n\nOnce complete, you can run class() to verify the class of the column\n\n# Check the class of the column\nclass(linelist$date_onset) \n\nOnce the values are in class Date, R will by default display them in the standard format, which is YYYY-MM-DD.\nNote that the above functions work best with 4-digit years. 2-digit years can produce unexpected results, as lubridate attempts to guess the century.\nTo convert a 2-digit year into a 4-digit year (all in the same century) you can convert to class character and then combine the existing digits with a pre-fix using str_glue() from the stringr package (see Characters and strings). Then convert to date.\n\ntwo_digit_years <- c(\"15\", \"15\", \"16\", \"17\")\nstr_glue(\"20{two_digit_years}\")\n\n2015\n2015\n2016\n2017\n\n\n\n\nCombine columns\nYou can use the lubridate functions make_date() and make_datetime() to combine multiple numeric columns into one date column. For example if you have numeric columns onset_day, onset_month, and onset_year in the data frame linelist:\n\nlinelist <- linelist %>% \n mutate(onset_date = make_date(year = onset_year, month = onset_month, day = onset_day))", + "text": "9.3 Convert to Date\nAfter importing a dataset into R, date column values may look like “1989/12/30”, “05/06/2014”, or “13 Jan 2020”. In these cases, R is likely still treating these values as Character values. R must be told that these values are dates… and what the format of the date is (which part is Day, which is Month, which is Year, etc).\nOnce told, R converts these values to class Date. In the background, R will store the dates as numbers (the number of days from its “origin” date 1 Jan 1970). You will not interface with the date number often, but this allows for R to treat dates as continuous variables and to allow special operations such as calculating the distance between dates.\nBy default, values of class Date in R are displayed as YYYY-MM-DD. Later in this section we will discuss how to change the display of date values.\nBelow we present two approaches to converting a column from character values to class Date.\nTIP: You can check the current class of a column with base R function class(), like class(linelist$date_onset).\n\nbase R\nas.Date() is the standard, base R function to convert an object or column to class Date (note capitalization of “D”).\nUse of as.Date() requires that:\n\nYou specify the existing format of the raw character date or the origin date if supplying dates as numbers (see section on Excel dates)\n\nIf used on a character column, all date values must have the same exact format (if this is not the case, try parse_date() from the parsedate package)\n\nFirst, check the class of your column with class() from base R. If you are unsure or confused about the class of your data (e.g. you see “POSIXct”, etc.) it can be easiest to first convert the column to class Character with as.character(), and then convert it to class Date.\nSecond, within the as.Date() function, use the format = argument to tell R the current format of the character date components - which characters refer to the month, the day, and the year, and how they are separated. If your values are already in one of R’s standard date formats (“YYYY-MM-DD” or “YYYY/MM/DD”) the format = argument is not necessary.\nTo format =, provide a character string (in quotes) that represents the current date format using the special “strptime” abbreviations below. For example, if your character dates are currently in the format “DD/MM/YYYY”, like “24/04/1968”, then you would use format = \"%d/%m/%Y\" to convert the values into dates. Putting the format in quotation marks is necessary. And don’t forget any slashes or dashes!\n\n# Convert to class date\nlinelist <- linelist %>% \n mutate(date_onset = as.Date(date_of_onset, format = \"%d/%m/%Y\"))\n\nMost of the strptime abbreviations are listed below. You can see the complete list by running ?strptime.\n%d = Day number of month (5, 17, 28, etc.)\n%j = Day number of the year (Julian day 001-366)\n%a = Abbreviated weekday (Mon, Tue, Wed, etc.)\n%A = Full weekday (Monday, Tuesday, etc.) %w = Weekday number (0-6, Sunday is 0)\n%u = Weekday number (1-7, Monday is 1)\n%W = Week number (00-53, Monday is week start)\n%U = Week number (01-53, Sunday is week start)\n%m = Month number (e.g. 01, 02, 03, 04)\n%b = Abbreviated month (Jan, Feb, etc.)\n%B = Full month (January, February, etc.)\n%y = 2-digit year (e.g. 89)\n%Y = 4-digit year (e.g. 1989)\n%h = hours (24-hr clock)\n%m = minutes\n%s = seconds %z = offset from GMT\n%Z = Time zone (character)\nTIP: The format = argument of as.Date() is not telling R the format you want the dates to be, but rather how to identify the date parts as they are before you run the command.\nTIP: Be sure that in the format = argument you use the date-part separator (e.g. /, -, or space) that is present in your dates.\nOnce the values are in class Date, R will by default display them in the standard format, which is YYYY-MM-DD.\n\n\nlubridate\nConverting character objects to dates can be made easier by using the lubridate package. This is a tidyverse package designed to make working with dates and times more simple and consistent than in base R. For these reasons, lubridate is often considered the gold-standard package for dates and time, and is recommended whenever working with them.\nThe lubridate package provides several different helper functions designed to convert character objects to dates in an intuitive, and more lenient way than specifying the format in as.Date(). These functions are specific to the rough date format, but allow for a variety of separators, and synonyms for dates (e.g. 01 vs Jan vs January) - they are named after abbreviations of date formats.\n\n# install/load lubridate \npacman::p_load(lubridate)\n\nThe ymd() function flexibly converts date values supplied as year, then month, then day.\n\n# read date in year-month-day format\nymd(\"2020-10-11\")\n\n[1] \"2020-10-11\"\n\nymd(\"20201011\")\n\n[1] \"2020-10-11\"\n\n\nThe mdy() function flexibly converts date values supplied as month, then day, then year.\n\n# read date in month-day-year format\nmdy(\"10/11/2020\")\n\n[1] \"2020-10-11\"\n\nmdy(\"Oct 11 20\")\n\n[1] \"2020-10-11\"\n\n\nThe dmy() function flexibly converts date values supplied as day, then month, then year.\n\n# read date in day-month-year format\ndmy(\"11 10 2020\")\n\n[1] \"2020-10-11\"\n\ndmy(\"11 October 2020\")\n\n[1] \"2020-10-11\"\n\n\n\n\n\n\nIf using piping, the conversion of a character column to dates with lubridate might look like this:\n\nlinelist <- linelist %>%\n mutate(date_onset = lubridate::dmy(date_onset))\n\nOnce complete, you can run class() to verify the class of the column\n\n# Check the class of the column\nclass(linelist$date_onset) \n\nOnce the values are in class Date, R will by default display them in the standard format, which is YYYY-MM-DD.\nNote that the above functions work best with 4-digit years. 2-digit years can produce unexpected results, as lubridate attempts to guess the century.\nTo convert a 2-digit year into a 4-digit year (all in the same century) you can convert to class character and then combine the existing digits with a pre-fix using str_glue() from the stringr package (see Characters and strings). Then convert to date.\n\ntwo_digit_years <- c(\"15\", \"15\", \"16\", \"17\")\nstr_glue(\"20{two_digit_years}\")\n\n2015\n2015\n2016\n2017\n\n\n\n\nCombine columns\nYou can use the lubridate functions make_date() and make_datetime() to combine multiple numeric columns into one date column. For example if you have numeric columns onset_day, onset_month, and onset_year in the data frame linelist:\n\nlinelist <- linelist %>% \n mutate(\n onset_date = make_date(\n year = onset_year, \n month = onset_month, \n day = onset_day)\n )", "crumbs": [ "Data Management", "9  Working with dates" @@ -802,7 +802,7 @@ "href": "new_pages/dates.html#messy-dates", "title": "9  Working with dates", "section": "9.5 Messy dates", - "text": "9.5 Messy dates\nThe function parse_date() from the parsedate package attempts to read a “messy” date column containing dates in many different formats and convert the dates to a standard format. You can read more online about parse_date().\nFor example parse_date() would see a vector of the following character dates “03 Jan 2018”, “07/03/1982”, and “08/20/85” and convert them to class Date as: 2018-01-03, 1982-03-07, and 1985-08-20.\n\nparsedate::parse_date(c(\"03 January 2018\",\n \"07/03/1982\",\n \"08/20/85\"))\n\n[1] \"2018-01-03 UTC\" \"1982-07-03 UTC\" \"1985-08-20 UTC\"\n\n\n\n# An example using parse_date() on the column date_onset\nlinelist <- linelist %>% \n mutate(date_onset = parse_date(date_onset))", + "text": "9.5 Messy dates\nThe function parse_date() from the parsedate package attempts to read a “messy” date column containing dates in many different formats and convert the dates to a standard format. You can read more online about parse_date().\nFor example parse_date() would see a vector of the following character dates “03 Jan 2018”, “07/03/1982”, and “08/20/85” and convert them to class Date as: 2018-01-03, 1982-03-07, and 1985-08-20.\n\nparsedate::parse_date(c(\"03 January 2018\",\n \"07/03/1982\",\n \"08/20/85\"))\n\n[1] \"2018-01-03 UTC\" \"1982-07-03 UTC\" \"1985-08-20 UTC\"\n\n\n\n# An example using parse_date() on the column date_onset\nlinelist <- linelist %>% \n mutate(date_onset = parse_date(date_onset))\n\nAnother function we can use is fix_date_df from the datefixR package. This package can take in a wide variety of different, messy, date formats at the same time and set them to a standard format!\n\npacman::p_load(\n datefixR\n)\n\n#Set up messy dates\nmessy_data_table <- data.frame(\n case = 1:6,\n onset_date = c(\"2014-01-01\",\n \"05 9 14\",\n \"2014 july\",\n \"1st Apr 2014\",\n \"15 le avril 2014\",\n 2015\n )\n )\n\nmessy_data_table\n\n case onset_date\n1 1 2014-01-01\n2 2 05 9 14\n3 3 2014 july\n4 4 1st Apr 2014\n5 5 15 le avril 2014\n6 6 2015\n\n#Fix the dates\nfix_date_df(\n df = messy_data_table,\n col.names = c(\"onset_date\")\n)\n\n case onset_date\n1 1 2014-01-01\n2 2 2014-09-05\n3 3 2014-07-01\n4 4 2014-04-01\n5 5 2014-04-15\n6 6 2015-07-01\n\n\nYou can see that even with this range of different formats (and languages!) it has managed to format all of our dates in the same way. Note that for dates missing a “month” it uses the default of 7 (July) and those missing a day it defaults to 1 (1st day of the month). These can be customised using the arguments day.impute = and month.impute =.\nFor a list of the languages that can be used, and additional functions and limitations, please see the website.", "crumbs": [ "Data Management", "9  Working with dates" @@ -813,7 +813,7 @@ "href": "new_pages/dates.html#working-with-date-time-class", "title": "9  Working with dates", "section": "9.6 Working with date-time class", - "text": "9.6 Working with date-time class\nAs previously mentioned, R also supports a datetime class - a column that contains date and time information. As with the Date class, these often need to be converted from character objects to datetime objects.\n\nConvert dates with times\nA standard datetime object is formatted with the date first, which is followed by a time component - for example 01 Jan 2020, 16:30. As with dates, there are many ways this can be formatted, and there are numerous levels of precision (hours, minutes, seconds) that can be supplied.\nLuckily, lubridate helper functions also exist to help convert these strings to datetime objects. These functions are extensions of the date helper functions, with _h (only hours supplied), _hm (hours and minutes supplied), or _hms (hours, minutes, and seconds supplied) appended to the end (e.g. dmy_hms()). These can be used as shown:\nConvert datetime with only hours to datetime object\n\nymd_h(\"2020-01-01 16hrs\")\n\n[1] \"2020-01-01 16:00:00 UTC\"\n\nymd_h(\"2020-01-01 4PM\")\n\n[1] \"2020-01-01 16:00:00 UTC\"\n\n\nConvert datetime with hours and minutes to datetime object\n\ndmy_hm(\"01 January 2020 16:20\")\n\n[1] \"2020-01-01 16:20:00 UTC\"\n\n\nConvert datetime with hours, minutes, and seconds to datetime object\n\nmdy_hms(\"01 January 2020, 16:20:40\")\n\n[1] \"2020-01-20 16:20:40 UTC\"\n\n\nYou can supply time zone but it is ignored. See section later in this page on time zones.\n\nmdy_hms(\"01 January 2020, 16:20:40 PST\")\n\n[1] \"2020-01-20 16:20:40 UTC\"\n\n\nWhen working with a data frame, time and date columns can be combined to create a datetime column using str_glue() from stringr package and an appropriate lubridate function. See the page on Characters and strings for details on stringr.\nIn this example, the linelist data frame has a column in format “hours:minutes”. To convert this to a datetime we follow a few steps:\n\nCreate a “clean” time of admission column with missing values filled-in with the column median. We do this because lubridate won’t operate on missing values. Combine it with the column date_hospitalisation, and then use the function ymd_hm() to convert.\n\n\n# packages\npacman::p_load(tidyverse, lubridate, stringr)\n\n# time_admission is a column in hours:minutes\nlinelist <- linelist %>%\n \n # when time of admission is not given, assign the median admission time\n mutate(\n time_admission_clean = ifelse(\n is.na(time_admission), # if time is missing\n median(time_admission), # assign the median\n time_admission # if not missing keep as is\n ) %>%\n \n # use str_glue() to combine date and time columns to create one character column\n # and then use ymd_hm() to convert it to datetime\n mutate(\n date_time_of_admission = str_glue(\"{date_hospitalisation} {time_admission_clean}\") %>% \n ymd_hm()\n )\n\n\n\nConvert times alone\nIf your data contain only a character time (hours and minutes), you can convert and manipulate them as times using strptime() from base R. For example, to get the difference between two of these times:\n\n# raw character times\ntime1 <- \"13:45\" \ntime2 <- \"15:20\"\n\n# Times converted to a datetime class\ntime1_clean <- strptime(time1, format = \"%H:%M\")\ntime2_clean <- strptime(time2, format = \"%H:%M\")\n\n# Difference is of class \"difftime\" by default, here converted to numeric hours \nas.numeric(time2_clean - time1_clean) # difference in hours\n\n[1] 1.583333\n\n\nNote however that without a date value provided, it assumes the date is today. To combine a string date and a string time together see how to use stringr in the section just above. Read more about strptime() here.\nTo convert single-digit numbers to double-digits (e.g. to “pad” hours or minutes with leading zeros to achieve 2 digits), see this “Pad length” section of the Characters and strings page.\n\n\nExtract time\nYou can extract elements of a time with hour(), minute(), or second() from lubridate.\nHere is an example of extracting the hour, and then classifing by part of the day. We begin with the column time_admission, which is class Character in format “HH:MM”. First, the strptime() is used as described above to convert the characters to datetime class. Then, the hour is extracted with hour(), returning a number from 0-24. Finally, a column time_period is created using logic with case_when() to classify rows into Morning/Afternoon/Evening/Night based on their hour of admission.\n\nlinelist <- linelist %>%\n mutate(hour_admit = hour(strptime(time_admission, format = \"%H:%M\"))) %>%\n mutate(time_period = case_when(\n hour_admit > 06 & hour_admit < 12 ~ \"Morning\",\n hour_admit >= 12 & hour_admit < 17 ~ \"Afternoon\",\n hour_admit >= 17 & hour_admit < 21 ~ \"Evening\",\n hour_admit >=21 | hour_admit <= 6 ~ \"Night\"))\n\nTo learn more about case_when() see the page on Cleaning data and core functions.", + "text": "9.6 Working with date-time class\nAs previously mentioned, R also supports a datetime class - a column that contains date and time information. As with the Date class, these often need to be converted from character objects to datetime objects.\n\nConvert dates with times\nA standard datetime object is formatted with the date first, which is followed by a time component - for example 01 Jan 2020, 16:30. As with dates, there are many ways this can be formatted, and there are numerous levels of precision (hours, minutes, seconds) that can be supplied.\nLuckily, lubridate helper functions also exist to help convert these strings to datetime objects. These functions are extensions of the date helper functions, with _h (only hours supplied), _hm (hours and minutes supplied), or _hms (hours, minutes, and seconds supplied) appended to the end (e.g. dmy_hms()). These can be used as shown:\nConvert datetime with only hours to datetime object\n\nymd_h(\"2020-01-01 16hrs\")\n\n[1] \"2020-01-01 16:00:00 UTC\"\n\nymd_h(\"2020-01-01 4PM\")\n\n[1] \"2020-01-01 16:00:00 UTC\"\n\n\nIf you are missing the time, for example when the datetime object is created by combining columns in a dataset where some of the information is unavailable, you may want to include the argument truncated =. This will allow incomplete data to be converted given the available information, rather than defaulting to NA. The value you input into truncated =, will depend on how many formats can be missing.\n\nymd_h(\"2020-01-01\")\n\nWarning: All formats failed to parse. No formats found.\n\n\n[1] NA\n\nymd_h(\"2020-01-01\", truncated = 2)\n\n[1] \"2020-01-01 UTC\"\n\n\nConvert datetime with hours and minutes to datetime object\n\ndmy_hm(\"01 January 2020 16:20\")\n\n[1] \"2020-01-01 16:20:00 UTC\"\n\n\nConvert datetime with hours, minutes, and seconds to datetime object\n\nmdy_hms(\"01 January 2020, 16:20:40\")\n\n[1] \"2020-01-20 16:20:40 UTC\"\n\n\nYou can supply time zone but it is ignored. See section later in this page on time zones.\n\nmdy_hms(\"01 January 2020, 16:20:40 PST\")\n\n[1] \"2020-01-20 16:20:40 UTC\"\n\n\nWhen working with a data frame, time and date columns can be combined to create a datetime column using str_glue() from stringr package and an appropriate lubridate function. See the page on Characters and strings for details on stringr.\nIn this example, the linelist data frame has a column in format “hours:minutes”. To convert this to a datetime we follow a few steps:\n\nCreate a “clean” time of admission column with missing values filled-in with the column median. We do this because lubridate won’t operate on missing values. Combine it with the column date_hospitalisation, and then use the function ymd_hm() to convert.\n\n\n# packages\npacman::p_load(\n tidyverse, \n lubridate, \n stringr\n )\n\n# time_admission is a column in hours:minutes\nlinelist <- linelist %>%\n \n # when time of admission is not given, assign the median admission time\n mutate(\n time_admission_clean = ifelse(\n is.na(time_admission), # if time is missing\n median(time_admission, na.rm = T), # assign the median\n time_admission # if not missing keep as is\n )) %>%\n \n # use str_glue() to combine date and time columns to create one character column\n # and then use ymd_hm() to convert it to datetime\n mutate(\n date_time_of_admission = str_glue(\"{date_hospitalisation} {time_admission_clean}\") %>% \n ymd_hm()\n )\n\n\n\nConvert times alone\nIf your data contain only a character time (hours and minutes), you can convert and manipulate them as times using strptime() from base R. For example, to get the difference between two of these times:\n\n# raw character times\ntime1 <- \"13:45\" \ntime2 <- \"15:20\"\n\n# Times converted to a datetime class\ntime1_clean <- strptime(time1, format = \"%H:%M\")\ntime2_clean <- strptime(time2, format = \"%H:%M\")\n\n# Difference is of class \"difftime\" by default, here converted to numeric hours \nas.numeric(time2_clean - time1_clean) # difference in hours\n\n[1] 1.583333\n\n\nNote however that without a date value provided, it assumes the date is today. To combine a string date and a string time together see how to use stringr in the section just above. Read more about strptime() here.\nTo convert single-digit numbers to double-digits (e.g. to “pad” hours or minutes with leading zeros to achieve 2 digits), see this “Pad length” section of the Characters and strings page.\n\n\nExtract time\nYou can extract elements of a time with hour(), minute(), or second() from lubridate.\nHere is an example of extracting the hour, and then classifing by part of the day. We begin with the column time_admission, which is class Character in format “HH:MM”. First, the strptime() is used as described above to convert the characters to datetime class. Then, the hour is extracted with hour(), returning a number from 0-24. Finally, a column time_period is created using logic with case_when() to classify rows into Morning/Afternoon/Evening/Night based on their hour of admission.\n\nlinelist <- linelist %>%\n mutate(hour_admit = hour(strptime(time_admission, format = \"%H:%M\"))) %>%\n mutate(time_period = case_when(\n hour_admit > 06 & hour_admit < 12 ~ \"Morning\",\n hour_admit >= 12 & hour_admit < 17 ~ \"Afternoon\",\n hour_admit >= 17 & hour_admit < 21 ~ \"Evening\",\n hour_admit >=21 | hour_admit <= 6 ~ \"Night\"))\n\nTo learn more about case_when() see the page on Cleaning data and core functions.", "crumbs": [ "Data Management", "9  Working with dates" @@ -824,7 +824,7 @@ "href": "new_pages/dates.html#working-with-dates", "title": "9  Working with dates", "section": "9.7 Working with dates", - "text": "9.7 Working with dates\nlubridate can also be used for a variety of other functions, such as extracting aspects of a date/datetime, performing date arithmetic, or calculating date intervals\nHere we define a date to use for the examples:\n\n# create object of class Date\nexample_date <- ymd(\"2020-03-01\")\n\n\nExtract date components\nYou can extract common aspects such as month, day, weekday:\n\nmonth(example_date) # month number\n\n[1] 3\n\nday(example_date) # day (number) of the month\n\n[1] 1\n\nwday(example_date) # day number of the week (1-7)\n\n[1] 1\n\n\nYou can also extract time components from a datetime object or column. This can be useful if you want to view the distribution of admission times.\n\nexample_datetime <- ymd_hm(\"2020-03-01 14:45\")\n\nhour(example_datetime) # extract hour\nminute(example_datetime) # extract minute\nsecond(example_datetime) # extract second\n\nThere are several options to retrieve weeks. See the section on Epidemiological weeks below.\nNote that if you are seeking to display a date a certain way (e.g. “Jan 2020” or “Thursday 20 March” or “Week 20, 1977”) you can do this more flexibly as described in the section on Date display.\n\n\nDate math\nYou can add certain numbers of days or weeks using their respective function from lubridate.\n\n# add 3 days to this date\nexample_date + days(3)\n\n[1] \"2020-03-04\"\n\n# add 7 weeks and subtract two days from this date\nexample_date + weeks(7) - days(2)\n\n[1] \"2020-04-17\"\n\n\n\n\nDate intervals\nThe difference between dates can be calculated by:\n\nEnsure both dates are of class date\n\nUse subtraction to return the “difftime” difference between the two dates\n\nIf necessary, convert the result to numeric class to perform subsequent mathematical calculations\n\nBelow the interval between two dates is calculated and displayed. You can find intervals by using the subtraction “minus” symbol on values that are class Date. Note, however that the class of the returned value is “difftime” as displayed below, and must be converted to numeric.\n\n# find the interval between this date and Feb 20 2020 \noutput <- example_date - ymd(\"2020-02-20\")\noutput # print\n\nTime difference of 10 days\n\nclass(output)\n\n[1] \"difftime\"\n\n\nTo do subsequent operations on a “difftime”, convert it to numeric with as.numeric().\nThis can all be brought together to work with data - for example:\n\npacman::p_load(lubridate, tidyverse) # load packages\n\nlinelist <- linelist %>%\n \n # convert date of onset from character to date objects by specifying dmy format\n mutate(date_onset = dmy(date_onset),\n date_hospitalisation = dmy(date_hospitalisation)) %>%\n \n # filter out all cases without onset in march\n filter(month(date_onset) == 3) %>%\n \n # find the difference in days between onset and hospitalisation\n mutate(days_onset_to_hosp = date_hospitalisation - date_of_onset)\n\nIn a data frame context, if either of the above dates is missing, the operation will fail for that row. This will result in an NA instead of a numeric value. When using this column for calculations, be sure to set the na.rm = argument to TRUE. For example:\n\n# calculate the median number of days to hospitalisation for all cases where data are available\nmedian(linelist_delay$days_onset_to_hosp, na.rm = T)", + "text": "9.7 Working with dates\nlubridate can also be used for a variety of other functions, such as extracting aspects of a date/datetime, performing date arithmetic, or calculating date intervals\nHere we define a date to use for the examples:\n\n# create object of class Date\nexample_date <- ymd(\"2020-03-01\")\n\n\nExtract date components\nYou can extract common aspects such as month, day, weekday:\n\nmonth(example_date) # month number\n\n[1] 3\n\nday(example_date) # day (number) of the month\n\n[1] 1\n\nwday(example_date) # day number of the week (1-7)\n\n[1] 1\n\n\nYou can also extract time components from a datetime object or column. This can be useful if you want to view the distribution of admission times.\n\nexample_datetime <- ymd_hm(\"2020-03-01 14:45\")\n\nhour(example_datetime) # extract hour\nminute(example_datetime) # extract minute\nsecond(example_datetime) # extract second\n\nThere are several options to retrieve weeks. See the section on Epidemiological weeks below.\nNote that if you are seeking to display a date a certain way (e.g. “Jan 2020” or “Thursday 20 March” or “Week 20, 1977”) you can do this more flexibly as described in the section on Date display.\n\n\nDate math\nYou can add certain numbers of days or weeks using their respective function from lubridate.\n\n# add 3 days to this date\nexample_date + days(3)\n\n[1] \"2020-03-04\"\n\n# add 7 weeks and subtract two days from this date\nexample_date + weeks(7) - days(2)\n\n[1] \"2020-04-17\"\n\n\n\n\nDate intervals\nThe difference between dates can be calculated by:\n\nEnsure both dates are of class date.\n\nUse subtraction to return the “difftime” difference between the two dates.\n\nIf necessary, convert the result to numeric class to perform subsequent mathematical calculations.\n\nBelow the interval between two dates is calculated and displayed. You can find intervals by using the subtraction “minus” symbol on values that are class Date. Note, however that the class of the returned value is “difftime” as displayed below, and must be converted to numeric.\n\n# find the interval between this date and Feb 20 2020 \noutput <- example_date - ymd(\"2020-02-20\")\noutput # print\n\nTime difference of 10 days\n\nclass(output)\n\n[1] \"difftime\"\n\n\nTo do subsequent operations on a “difftime”, convert it to numeric with as.numeric().\nThis can all be brought together to work with data - for example:\n\npacman::p_load(lubridate, tidyverse) # load packages\n\nlinelist <- linelist %>%\n \n # convert date of onset from character to date objects by specifying dmy format\n mutate(date_onset = dmy(date_onset),\n date_hospitalisation = dmy(date_hospitalisation)) %>%\n \n # filter out all cases without onset in march\n filter(month(date_onset) == 3) %>%\n \n # find the difference in days between onset and hospitalisation\n mutate(days_onset_to_hosp = date_hospitalisation - date_of_onset)\n\nIn a data frame context, if either of the above dates is missing, the operation will fail for that row. This will result in an NA instead of a numeric value. When using this column for calculations, be sure to set the na.rm = argument to TRUE. For example:\n\n# calculate the median number of days to hospitalisation for all cases where data are available\nmedian(linelist_delay$days_onset_to_hosp, na.rm = T)", "crumbs": [ "Data Management", "9  Working with dates" @@ -835,7 +835,7 @@ "href": "new_pages/dates.html#date-display", "title": "9  Working with dates", "section": "9.8 Date display", - "text": "9.8 Date display\nOnce dates are the correct class, you often want them to display differently, for example to display as “Monday 05 January” instead of “2018-01-05”. You may also want to adjust the display in order to then group rows by the date elements displayed - for example to group by month-year.\n\nformat()\nAdjust date display with the base R function format(). This function accepts a character string (in quotes) specifying the desired output format in the “%” strptime abbreviations (the same syntax as used in as.Date()). Below are most of the common abbreviations.\nNote: using format() will convert the values to class Character, so this is generally used towards the end of an analysis or for display purposes only! You can see the complete list by running ?strptime.\n%d = Day number of month (5, 17, 28, etc.)\n%j = Day number of the year (Julian day 001-366)\n%a = Abbreviated weekday (Mon, Tue, Wed, etc.)\n%A = Full weekday (Monday, Tuesday, etc.)\n%w = Weekday number (0-6, Sunday is 0)\n%u = Weekday number (1-7, Monday is 1)\n%W = Week number (00-53, Monday is week start)\n%U = Week number (01-53, Sunday is week start)\n%m = Month number (e.g. 01, 02, 03, 04)\n%b = Abbreviated month (Jan, Feb, etc.)\n%B = Full month (January, February, etc.)\n%y = 2-digit year (e.g. 89)\n%Y = 4-digit year (e.g. 1989)\n%h = hours (24-hr clock)\n%m = minutes\n%s = seconds\n%z = offset from GMT\n%Z = Time zone (character)\nAn example of formatting today’s date:\n\n# today's date, with formatting\nformat(Sys.Date(), format = \"%d %B %Y\")\n\n[1] \"19 June 2024\"\n\n# easy way to get full date and time (default formatting)\ndate()\n\n[1] \"Wed Jun 19 12:27:20 2024\"\n\n# formatted combined date, time, and time zone using str_glue() function\nstr_glue(\"{format(Sys.Date(), format = '%A, %B %d %Y, %z %Z, ')}{format(Sys.time(), format = '%H:%M:%S')}\")\n\nWednesday, June 19 2024, +0000 UTC, 12:27:20\n\n# Using format to display weeks\nformat(Sys.Date(), \"%Y Week %W\")\n\n[1] \"2024 Week 25\"\n\n\nNote that if using str_glue(), be aware of that within the expected double quotes ” you should only use single quotes (as above).\n\n\nMonth-Year\nTo convert a Date column to Month-year format, we suggest you use the function as.yearmon() from the zoo package. This converts the date to class “yearmon” and retains the proper ordering. In contrast, using format(column, \"%Y %B\") will convert to class Character and will order the values alphabetically (incorrectly).\nBelow, a new column yearmonth is created from the column date_onset, using the as.yearmon() function. The default (correct) ordering of the resulting values are shown in the table.\n\n# create new column \ntest_zoo <- linelist %>% \n mutate(yearmonth = zoo::as.yearmon(date_onset))\n\n# print table\ntable(test_zoo$yearmon)\n\n\nApr 2014 May 2014 Jun 2014 Jul 2014 Aug 2014 Sep 2014 Oct 2014 Nov 2014 \n 7 64 100 226 528 1070 1112 763 \nDec 2014 Jan 2015 Feb 2015 Mar 2015 Apr 2015 \n 562 431 306 277 186 \n\n\nIn contrast, you can see how only using format() does achieve the desired display format, but not the correct ordering.\n\n# create new column\ntest_format <- linelist %>% \n mutate(yearmonth = format(date_onset, \"%b %Y\"))\n\n# print table\ntable(test_format$yearmon)\n\n\nApr 2014 Apr 2015 Aug 2014 Dec 2014 Feb 2015 Jan 2015 Jul 2014 Jun 2014 \n 7 186 528 562 306 431 226 100 \nMar 2015 May 2014 Nov 2014 Oct 2014 Sep 2014 \n 277 64 763 1112 1070 \n\n\nNote: if you are working within a ggplot() and want to adjust how dates are displayed only, it may be sufficient to provide a strptime format to the date_labels = argument in scale_x_date() - you can use \"%b %Y\" or \"%Y %b\". See the ggplot tips page.\nzoo also offers the function as.yearqtr(), and you can use scale_x_yearmon() when using ggplot().", + "text": "9.8 Date display\nOnce dates are the correct class, you often want them to display differently, for example to display as “Monday 05 January” instead of “2018-01-05”. You may also want to adjust the display in order to then group rows by the date elements displayed - for example to group by month-year.\n\nformat()\nAdjust date display with the base R function format(). This function accepts a character string (in quotes) specifying the desired output format in the “%” strptime abbreviations (the same syntax as used in as.Date()). Below are most of the common abbreviations.\nNote: using format() will convert the values to class Character, so this is generally used towards the end of an analysis or for display purposes only! You can see the complete list by running ?strptime.\n%d = Day number of month (5, 17, 28, etc.)\n%j = Day number of the year (Julian day 001-366)\n%a = Abbreviated weekday (Mon, Tue, Wed, etc.)\n%A = Full weekday (Monday, Tuesday, etc.)\n%w = Weekday number (0-6, Sunday is 0)\n%u = Weekday number (1-7, Monday is 1)\n%W = Week number (00-53, Monday is week start)\n%U = Week number (01-53, Sunday is week start)\n%m = Month number (e.g. 01, 02, 03, 04)\n%b = Abbreviated month (Jan, Feb, etc.)\n%B = Full month (January, February, etc.)\n%y = 2-digit year (e.g. 89)\n%Y = 4-digit year (e.g. 1989)\n%h = hours (24-hr clock)\n%m = minutes\n%s = seconds\n%z = offset from GMT\n%Z = Time zone (character)\nAn example of formatting today’s date:\n\n# today's date, with formatting\nformat(Sys.Date(), format = \"%d %B %Y\")\n\n[1] \"14 October 2024\"\n\n# easy way to get full date and time (default formatting)\ndate()\n\n[1] \"Mon Oct 14 17:00:55 2024\"\n\n# formatted combined date, time, and time zone using str_glue() function\nstr_glue(\"{format(Sys.Date(), format = '%A, %B %d %Y, %z %Z, ')}{format(Sys.time(), format = '%H:%M:%S')}\")\n\nMonday, October 14 2024, +0000 UTC, 17:00:55\n\n# Using format to display weeks\nformat(Sys.Date(), \"%Y Week %W\")\n\n[1] \"2024 Week 42\"\n\n\nNote that if using str_glue(), be aware of that within the expected double quotes ” you should only use single quotes (as above).\n\n\nMonth-Year\nTo convert a Date column to Month-year format, we suggest you use the function as.yearmon() from the zoo package. This converts the date to class “yearmon” and retains the proper ordering. In contrast, using format(column, \"%Y %B\") will convert to class Character and will order the values alphabetically (incorrectly).\nBelow, a new column yearmonth is created from the column date_onset, using the as.yearmon() function. The default (correct) ordering of the resulting values are shown in the table.\n\n# create new column \ntest_zoo <- linelist %>% \n mutate(yearmonth = zoo::as.yearmon(date_onset))\n\n# print table\ntable(test_zoo$yearmon)\n\n\nApr 2014 May 2014 Jun 2014 Jul 2014 Aug 2014 Sep 2014 Oct 2014 Nov 2014 \n 7 64 100 226 528 1070 1112 763 \nDec 2014 Jan 2015 Feb 2015 Mar 2015 Apr 2015 \n 562 431 306 277 186 \n\n\nIn contrast, you can see how only using format() does achieve the desired display format, but not the correct ordering.\n\n# create new column\ntest_format <- linelist %>% \n mutate(yearmonth = format(date_onset, \"%b %Y\"))\n\n# print table\ntable(test_format$yearmon)\n\n\nApr 2014 Apr 2015 Aug 2014 Dec 2014 Feb 2015 Jan 2015 Jul 2014 Jun 2014 \n 7 186 528 562 306 431 226 100 \nMar 2015 May 2014 Nov 2014 Oct 2014 Sep 2014 \n 277 64 763 1112 1070 \n\n\nNote: if you are working within a ggplot() and want to adjust how dates are displayed only, it may be sufficient to provide a strptime format to the date_labels = argument in scale_x_date() - you can use \"%b %Y\" or \"%Y %b\". See the ggplot tips page.\nzoo also offers the function as.yearqtr(), and you can use scale_x_yearmon() when using ggplot().", "crumbs": [ "Data Management", "9  Working with dates" @@ -857,7 +857,7 @@ "href": "new_pages/dates.html#converting-datestime-zones", "title": "9  Working with dates", "section": "9.10 Converting dates/time zones", - "text": "9.10 Converting dates/time zones\nWhen data is present in different time time zones, it can often be important to standardise this data in a unified time zone. This can present a further challenge, as the time zone component of data must be coded manually in most cases.\nIn R, each datetime object has a timezone component. By default, all datetime objects will carry the local time zone for the computer being used - this is generally specific to a location rather than a named timezone, as time zones will often change in locations due to daylight savings time. It is not possible to accurately compensate for time zones without a time component of a date, as the event a date column represents cannot be attributed to a specific time, and therefore time shifts measured in hours cannot be reasonably accounted for.\nTo deal with time zones, there are a number of helper functions in lubridate that can be used to change the time zone of a datetime object from the local time zone to a different time zone. Time zones are set by attributing a valid tz database time zone to the datetime object. A list of these can be found here - if the location you are using data from is not on this list, nearby large cities in the time zone are available and serve the same purpose.\nhttps://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\n# assign the current time to a column\ntime_now <- Sys.time()\ntime_now\n\n[1] \"2024-06-19 12:27:21 CEST\"\n\n# use with_tz() to assign a new timezone to the column, while CHANGING the clock time\ntime_london_real <- with_tz(time_now, \"Europe/London\")\n\n# use force_tz() to assign a new timezone to the column, while KEEPING the clock time\ntime_london_local <- force_tz(time_now, \"Europe/London\")\n\n\n# note that as long as the computer that was used to run this code is NOT set to London time,\n# there will be a difference in the times \n# (the number of hours difference from the computers time zone to london)\ntime_london_real - time_london_local\n\nTime difference of -1 hours\n\n\nThis may seem largely abstract, and is often not needed if the user isn’t working across time zones.", + "text": "9.10 Converting dates/time zones\nWhen data is present in different time time zones, it can often be important to standardise this data in a unified time zone. This can present a further challenge, as the time zone component of data must be coded manually in most cases.\nIn R, each datetime object has a timezone component. By default, all datetime objects will carry the local time zone for the computer being used - this is generally specific to a location rather than a named timezone, as time zones will often change in locations due to daylight savings time. It is not possible to accurately compensate for time zones without a time component of a date, as the event a date column represents cannot be attributed to a specific time, and therefore time shifts measured in hours cannot be reasonably accounted for.\nTo deal with time zones, there are a number of helper functions in lubridate that can be used to change the time zone of a datetime object from the local time zone to a different time zone. Time zones are set by attributing a valid tz database time zone to the datetime object. A list of these can be found here - if the location you are using data from is not on this list, nearby large cities in the time zone are available and serve the same purpose.\nhttps://en.wikipedia.org/wiki/List_of_tz_database_time_zones\n\n# assign the current time to a column\ntime_now <- Sys.time()\ntime_now\n\n[1] \"2024-10-14 17:00:55 PDT\"\n\n# use with_tz() to assign a new timezone to the column, while CHANGING the clock time\ntime_london_real <- with_tz(time_now, \"Europe/London\")\n\n# use force_tz() to assign a new timezone to the column, while KEEPING the clock time\ntime_london_local <- force_tz(time_now, \"Europe/London\")\n\n\n# note that as long as the computer that was used to run this code is NOT set to London time,\n# there will be a difference in the times \n# (the number of hours difference from the computers time zone to london)\ntime_london_real - time_london_local\n\nTime difference of 8 hours\n\n\nThis may seem largely abstract, and is often not needed if the user isn’t working across time zones.", "crumbs": [ "Data Management", "9  Working with dates" @@ -868,7 +868,7 @@ "href": "new_pages/dates.html#lagging-and-leading-calculations", "title": "9  Working with dates", "section": "9.11 Lagging and leading calculations", - "text": "9.11 Lagging and leading calculations\nlead() and lag() are functions from the dplyr package which help find previous (lagged) or subsequent (leading) values in a vector - typically a numeric or date vector. This is useful when doing calculations of change/difference between time units.\nLet’s say you want to calculate the difference in cases between a current week and the previous one. The data are initially provided in weekly counts as shown below.\n\n\n\n\n\n\nWhen using lag() or lead() the order of rows in the dataframe is very important! - pay attention to whether your dates/numbers are ascending or descending\nFirst, create a new column containing the value of the previous (lagged) week.\n\nControl the number of units back/forward with n = (must be a non-negative integer)\n\nUse default = to define the value placed in non-existing rows (e.g. the first row for which there is no lagged value). By default this is NA.\n\nUse order_by = TRUE if your the rows are not ordered by your reference column\n\n\ncounts <- counts %>% \n mutate(cases_prev_wk = lag(cases_wk, n = 1))\n\n\n\n\n\n\n\nNext, create a new column which is the difference between the two cases columns:\n\ncounts <- counts %>% \n mutate(cases_prev_wk = lag(cases_wk, n = 1),\n case_diff = cases_wk - cases_prev_wk)\n\n\n\n\n\n\n\nYou can read more about lead() and lag() in the documentation here or by entering ?lag in your console.", + "text": "9.11 Lagging and leading calculations\nlead() and lag() are functions from the dplyr package which help find previous (lagged) or subsequent (leading) values in a vector - typically a numeric or date vector. This is useful when doing calculations of change/difference between time units.\nLet’s say you want to calculate the difference in cases between a current week and the previous one. The data are initially provided in weekly counts as shown below.\n\n\n\n\n\n\nWhen using lag() or lead() the order of rows in the dataframe is very important! - pay attention to whether your dates/numbers are ascending or descending.\nFirst, create a new column containing the value of the previous (lagged) week.\n\nControl the number of units back/forward with n = (must be a non-negative integer).\n\nUse default = to define the value placed in non-existing rows (e.g. the first row for which there is no lagged value). By default this is NA.\n\nUse order_by = TRUE if your the rows are not ordered by your reference column.\n\n\ncounts <- counts %>% \n mutate(cases_prev_wk = lag(cases_wk, n = 1))\n\n\n\n\n\n\n\nNext, create a new column which is the difference between the two cases columns:\n\ncounts <- counts %>% \n mutate(cases_prev_wk = lag(cases_wk, n = 1),\n case_diff = cases_wk - cases_prev_wk)\n\n\n\n\n\n\n\nYou can read more about lead() and lag() in the documentation here or by entering ?lag in your console.", "crumbs": [ "Data Management", "9  Working with dates" @@ -901,7 +901,7 @@ "href": "new_pages/characters_strings.html#preparation", "title": "10  Characters and strings", "section": "", - "text": "Load packages\nInstall or load the stringr and other tidyverse packages.\n\n# install/load packages\npacman::p_load(\n stringr, # many functions for handling strings\n tidyverse, # for optional data manipulation\n tools) # alternative for converting to title case\n\n\n\nImport data\nIn this page we will occassionally reference the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export(importing.qmd) page for details).\n\n# import case linelist \nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.", + "text": "Load packages\nInstall or load the stringr and other tidyverse packages.\n\n# install/load packages\npacman::p_load(\n stringr, # many functions for handling strings\n tidyverse, # for optional data manipulation\n tools # alternative for converting to title case\n ) \n\n\n\nImport data\nIn this page we will occassionally reference the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import case linelist \nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.", "crumbs": [ "Data Management", "10  Characters and strings" @@ -912,7 +912,7 @@ "href": "new_pages/characters_strings.html#unite-split-and-arrange", "title": "10  Characters and strings", "section": "10.2 Unite, split, and arrange", - "text": "10.2 Unite, split, and arrange\nThis section covers:\n\nUsing str_c(), str_glue(), and unite() to combine strings\n\nUsing str_order() to arrange strings\n\nUsing str_split() and separate() to split strings\n\n\n\nCombine strings\nTo combine or concatenate multiple strings into one string, we suggest using str_c from stringr. If you have distinct character values to combine, simply provide them as unique arguments, separated by commas.\n\nstr_c(\"String1\", \"String2\", \"String3\")\n\n[1] \"String1String2String3\"\n\n\nThe argument sep = inserts a character value between each of the arguments you provided (e.g. inserting a comma, space, or newline \"\\n\")\n\nstr_c(\"String1\", \"String2\", \"String3\", sep = \", \")\n\n[1] \"String1, String2, String3\"\n\n\nThe argument collapse = is relevant if you are inputting multiple vectors as arguments to str_c(). It is used to separate the elements of what would be an output vector, such that the output vector only has one long character element.\nThe example below shows the combination of two vectors into one (first names and last names). Another similar example might be jurisdictions and their case counts. In this example:\n\nThe sep = value appears between each first and last name\n\nThe collapse = value appears between each person\n\n\nfirst_names <- c(\"abdul\", \"fahruk\", \"janice\") \nlast_names <- c(\"hussein\", \"akinleye\", \"okeke\")\n\n# sep displays between the respective input strings, while collapse displays between the elements produced\nstr_c(first_names, last_names, sep = \" \", collapse = \"; \")\n\n[1] \"abdul hussein; fahruk akinleye; janice okeke\"\n\n\nNote: Depending on your desired display context, when printing such a combined string with newlines, you may need to wrap the whole phrase in cat() for the newlines to print properly:\n\n# For newlines to print correctly, the phrase may need to be wrapped in cat()\ncat(str_c(first_names, last_names, sep = \" \", collapse = \";\\n\"))\n\nabdul hussein;\nfahruk akinleye;\njanice okeke\n\n\n\n\n\nDynamic strings\nUse str_glue() to insert dynamic R code into a string. This is a very useful function for creating dynamic plot captions, as demonstrated below.\n\nAll content goes between double quotation marks str_glue(\"\")\n\nAny dynamic code or references to pre-defined values are placed within curly brackets {} within the double quotation marks. There can be many curly brackets in the same str_glue() command.\n\nTo display character quotes ’’, use single quotes within the surrounding double quotes (e.g. when providing date format - see example below)\n\nTip: You can use \\n to force a new line\n\nTip: You use format() to adjust date display, and use Sys.Date() to display the current date\n\nA simple example, of a dynamic plot caption:\n\nstr_glue(\"Data include {nrow(linelist)} cases and are current to {format(Sys.Date(), '%d %b %Y')}.\")\n\nData include 5888 cases and are current to 19 Jun 2024.\n\n\nAn alternative format is to use placeholders within the brackets and define the code in separate arguments at the end of the str_glue() function, as below. This can improve code readability if the text is long.\n\nstr_glue(\"Linelist as of {current_date}.\\nLast case hospitalized on {last_hospital}.\\n{n_missing_onset} cases are missing date of onset and not shown\",\n current_date = format(Sys.Date(), '%d %b %Y'),\n last_hospital = format(as.Date(max(linelist$date_hospitalisation, na.rm=T)), '%d %b %Y'),\n n_missing_onset = nrow(linelist %>% filter(is.na(date_onset)))\n )\n\nLinelist as of 19 Jun 2024.\nLast case hospitalized on 30 Apr 2015.\n256 cases are missing date of onset and not shown\n\n\nPulling from a data frame\nSometimes, it is useful to pull data from a data frame and have it pasted together in sequence. Below is an example data frame. We will use it to to make a summary statement about the jurisdictions and the new and total case counts.\n\n# make case data frame\ncase_table <- data.frame(\n zone = c(\"Zone 1\", \"Zone 2\", \"Zone 3\", \"Zone 4\", \"Zone 5\"),\n new_cases = c(3, 0, 7, 0, 15),\n total_cases = c(40, 4, 25, 10, 103)\n )\n\n\n\n\n\n\n\nUse str_glue_data(), which is specially made for taking data from data frame rows:\n\ncase_table %>% \n str_glue_data(\"{zone}: {new_cases} ({total_cases} total cases)\")\n\nZone 1: 3 (40 total cases)\nZone 2: 0 (4 total cases)\nZone 3: 7 (25 total cases)\nZone 4: 0 (10 total cases)\nZone 5: 15 (103 total cases)\n\n\nCombine strings across rows\nIf you are trying to “roll-up” values in a data frame column, e.g. combine values from multiple rows into just one row by pasting them together with a separator, see the section of the De-duplication page on “rolling-up” values.\nData frame to one line\nYou can make the statement appear in one line using str_c() (specifying the data frame and column names), and providing sep = and collapse = arguments.\n\nstr_c(case_table$zone, case_table$new_cases, sep = \" = \", collapse = \"; \")\n\n[1] \"Zone 1 = 3; Zone 2 = 0; Zone 3 = 7; Zone 4 = 0; Zone 5 = 15\"\n\n\nYou could add the pre-fix text “New Cases:” to the beginning of the statement by wrapping with a separate str_c() (if “New Cases:” was within the original str_c() it would appear multiple times).\n\nstr_c(\"New Cases: \", str_c(case_table$zone, case_table$new_cases, sep = \" = \", collapse = \"; \"))\n\n[1] \"New Cases: Zone 1 = 3; Zone 2 = 0; Zone 3 = 7; Zone 4 = 0; Zone 5 = 15\"\n\n\n\n\nUnite columns\nWithin a data frame, bringing together character values from multiple columns can be achieved with unite() from tidyr. This is the opposite of separate().\nProvide the name of the new united column. Then provide the names of the columns you wish to unite.\n\nBy default, the separator used in the united column is underscore _, but this can be changed with the sep = argument.\n\nremove = removes the input columns from the data frame (TRUE by default)\n\nna.rm = removes missing values while uniting (FALSE by default)\n\nBelow, we define a mini-data frame to demonstrate with:\n\ndf <- data.frame(\n case_ID = c(1:6),\n symptoms = c(\"jaundice, fever, chills\", # patient 1\n \"chills, aches, pains\", # patient 2 \n \"fever\", # patient 3\n \"vomiting, diarrhoea\", # patient 4\n \"bleeding from gums, fever\", # patient 5\n \"rapid pulse, headache\"), # patient 6\n outcome = c(\"Recover\", \"Death\", \"Death\", \"Recover\", \"Recover\", \"Recover\"))\n\n\ndf_split <- separate(df, symptoms, into = c(\"sym_1\", \"sym_2\", \"sym_3\"), extra = \"merge\")\n\nWarning: Expected 3 pieces. Missing pieces filled with `NA` in 2 rows [3, 4].\n\n\nHere is the example data frame:\n\n\n\n\n\n\nBelow, we unite the three symptom columns:\n\ndf_split %>% \n unite(\n col = \"all_symptoms\", # name of the new united column\n c(\"sym_1\", \"sym_2\", \"sym_3\"), # columns to unite\n sep = \", \", # separator to use in united column\n remove = TRUE, # if TRUE, removes input cols from the data frame\n na.rm = TRUE # if TRUE, missing values are removed before uniting\n )\n\n case_ID all_symptoms outcome\n1 1 jaundice, fever, chills Recover\n2 2 chills, aches, pains Death\n3 3 fever Death\n4 4 vomiting, diarrhoea Recover\n5 5 bleeding, from, gums, fever Recover\n6 6 rapid, pulse, headache Recover\n\n\n\n\n\nSplit\nTo split a string based on a pattern, use str_split(). It evaluates the string(s) and returns a list of character vectors consisting of the newly-split values.\nThe simple example below evaluates one string and splits it into three. By default it returns an object of class list with one element (a character vector) for each string initially provided. If simplify = TRUE it returns a character matrix.\nIn this example, one string is provided, and the function returns a list with one element - a character vector with three values.\n\nstr_split(string = \"jaundice, fever, chills\",\n pattern = \",\")\n\n[[1]]\n[1] \"jaundice\" \" fever\" \" chills\" \n\n\nIf the output is saved, you can then access the nth split value with bracket syntax. To access a specific value you can use syntax like this: the_returned_object[[1]][2], which would access the second value from the first evaluated string (“fever”). See the R basics page for more detail on accessing elements.\n\npt1_symptoms <- str_split(\"jaundice, fever, chills\", \",\")\n\npt1_symptoms[[1]][2] # extracts 2nd value from 1st (and only) element of the list\n\n[1] \" fever\"\n\n\nIf multiple strings are provided by str_split(), there will be more than one element in the returned list.\n\nsymptoms <- c(\"jaundice, fever, chills\", # patient 1\n \"chills, aches, pains\", # patient 2 \n \"fever\", # patient 3\n \"vomiting, diarrhoea\", # patient 4\n \"bleeding from gums, fever\", # patient 5\n \"rapid pulse, headache\") # patient 6\n\nstr_split(symptoms, \",\") # split each patient's symptoms\n\n[[1]]\n[1] \"jaundice\" \" fever\" \" chills\" \n\n[[2]]\n[1] \"chills\" \" aches\" \" pains\"\n\n[[3]]\n[1] \"fever\"\n\n[[4]]\n[1] \"vomiting\" \" diarrhoea\"\n\n[[5]]\n[1] \"bleeding from gums\" \" fever\" \n\n[[6]]\n[1] \"rapid pulse\" \" headache\" \n\n\nTo return a “character matrix” instead, which may be useful if creating data frame columns, set the argument simplify = TRUE as shown below:\n\nstr_split(symptoms, \",\", simplify = TRUE)\n\n [,1] [,2] [,3] \n[1,] \"jaundice\" \" fever\" \" chills\"\n[2,] \"chills\" \" aches\" \" pains\" \n[3,] \"fever\" \"\" \"\" \n[4,] \"vomiting\" \" diarrhoea\" \"\" \n[5,] \"bleeding from gums\" \" fever\" \"\" \n[6,] \"rapid pulse\" \" headache\" \"\" \n\n\nYou can also adjust the number of splits to create with the n = argument. For example, this restricts the number of splits to 2. Any further commas remain within the second values.\n\nstr_split(symptoms, \",\", simplify = TRUE, n = 2)\n\n [,1] [,2] \n[1,] \"jaundice\" \" fever, chills\"\n[2,] \"chills\" \" aches, pains\" \n[3,] \"fever\" \"\" \n[4,] \"vomiting\" \" diarrhoea\" \n[5,] \"bleeding from gums\" \" fever\" \n[6,] \"rapid pulse\" \" headache\" \n\n\nNote - the same outputs can be achieved with str_split_fixed(), in which you do not give the simplify argument, but must instead designate the number of columns (n).\n\nstr_split_fixed(symptoms, \",\", n = 2)\n\n\n\nSplit columns\nIf you are trying to split data frame column, it is best to use the separate() function from dplyr. It is used to split one character column into other columns.\nLet’s say we have a simple data frame df (defined and united in the unite section) containing a case_ID column, one character column with many symptoms, and one outcome column. Our goal is to separate the symptoms column into many columns - each one containing one symptom.\n\n\n\n\n\n\nAssuming the data are piped into separate(), first provide the column to be separated. Then provide into = as a vector c( ) containing the new columns names, as shown below.\n\nsep = the separator, can be a character, or a number (interpreted as the character position to split at)\nremove = FALSE by default, removes the input column\n\nconvert = FALSE by default, will cause string “NA”s to become NA\n\nextra = this controls what happens if there are more values created by the separation than new columns named.\n\nextra = \"warn\" means you will see a warning but it will drop excess values (the default)\n\nextra = \"drop\" means the excess values will be dropped with no warning\n\nextra = \"merge\" will only split to the number of new columns listed in into - this setting will preserve all your data\n\n\nAn example with extra = \"merge\" is below - no data is lost. Two new columns are defined but any third symptoms are left in the second new column:\n\n# third symptoms combined into second new column\ndf %>% \n separate(symptoms, into = c(\"sym_1\", \"sym_2\"), sep=\",\", extra = \"merge\")\n\nWarning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].\n\n\n case_ID sym_1 sym_2 outcome\n1 1 jaundice fever, chills Recover\n2 2 chills aches, pains Death\n3 3 fever <NA> Death\n4 4 vomiting diarrhoea Recover\n5 5 bleeding from gums fever Recover\n6 6 rapid pulse headache Recover\n\n\nWhen the default extra = \"drop\" is used below, a warning is given but the third symptoms are lost:\n\n# third symptoms are lost\ndf %>% \n separate(symptoms, into = c(\"sym_1\", \"sym_2\"), sep=\",\")\n\nWarning: Expected 2 pieces. Additional pieces discarded in 2 rows [1, 2].\n\n\nWarning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].\n\n\n case_ID sym_1 sym_2 outcome\n1 1 jaundice fever Recover\n2 2 chills aches Death\n3 3 fever <NA> Death\n4 4 vomiting diarrhoea Recover\n5 5 bleeding from gums fever Recover\n6 6 rapid pulse headache Recover\n\n\nCAUTION: If you do not provide enough into values for the new columns, your data may be truncated.\n\n\n\nArrange alphabetically\nSeveral strings can be sorted by alphabetical order. str_order() returns the order, while str_sort() returns the strings in that order.\n\n# strings\nhealth_zones <- c(\"Alba\", \"Takota\", \"Delta\")\n\n# return the alphabetical order\nstr_order(health_zones)\n\n[1] 1 3 2\n\n# return the strings in alphabetical order\nstr_sort(health_zones)\n\n[1] \"Alba\" \"Delta\" \"Takota\"\n\n\nTo use a different alphabet, add the argument locale =. See the full list of locales by entering stringi::stri_locale_list() in the R console.\n\n\n\nbase R functions\nIt is common to see base R functions paste() and paste0(), which concatenate vectors after converting all parts to character. They act similarly to str_c() but the syntax is arguably more complicated - in the parentheses each part is separated by a comma. The parts are either character text (in quotes) or pre-defined code objects (no quotes). For example:\n\nn_beds <- 10\nn_masks <- 20\n\npaste0(\"Regional hospital needs \", n_beds, \" beds and \", n_masks, \" masks.\")\n\n[1] \"Regional hospital needs 10 beds and 20 masks.\"\n\n\nsep = and collapse = arguments can be specified. paste() is simply paste0() with a default sep = \" \" (one space).", + "text": "10.2 Unite, split, and arrange\nThis section covers:\n\nUsing str_c(), str_glue(), and unite() to combine strings.\n\nUsing str_order() to arrange strings.\n\nUsing str_split() and separate() to split strings.\n\n\n\nCombine strings\nTo combine or concatenate multiple strings into one string, we suggest using str_c from stringr. If you have distinct character values to combine, simply provide them as unique arguments, separated by commas.\n\nstr_c(\"String1\", \"String2\", \"String3\")\n\n[1] \"String1String2String3\"\n\n\nThe argument sep = inserts a character value between each of the arguments you provided (e.g. inserting a comma, space, or newline \"\\n\")\n\nstr_c(\"String1\", \"String2\", \"String3\", sep = \", \")\n\n[1] \"String1, String2, String3\"\n\n\nThe argument collapse = is relevant if you are inputting multiple vectors as arguments to str_c(). It is used to separate the elements of what would be an output vector, such that the output vector only has one long character element.\nThe example below shows the combination of two vectors into one (first names and last names). Another similar example might be jurisdictions and their case counts. In this example:\n\nThe sep = value appears between each first and last name\n\nThe collapse = value appears between each person\n\n\nfirst_names <- c(\"abdul\", \"fahruk\", \"janice\") \nlast_names <- c(\"hussein\", \"akinleye\", \"okeke\")\n\n# sep displays between the respective input strings, while collapse displays between the elements produced\nstr_c(first_names, last_names, sep = \" \", collapse = \"; \")\n\n[1] \"abdul hussein; fahruk akinleye; janice okeke\"\n\n\nNote: Depending on your desired display context, when printing such a combined string with newlines, you may need to wrap the whole phrase in cat() for the newlines to print properly:\n\n# For newlines to print correctly, the phrase may need to be wrapped in cat()\ncat(str_c(first_names, last_names, sep = \" \", collapse = \";\\n\"))\n\nabdul hussein;\nfahruk akinleye;\njanice okeke\n\n\n\n\n\nDynamic strings\nUse str_glue() to insert dynamic R code into a string. This is a very useful function for creating dynamic plot captions, as demonstrated below.\n\nAll content goes between double quotation marks str_glue(\"\").\n\nAny dynamic code or references to pre-defined values are placed within curly brackets {} within the double quotation marks. There can be many curly brackets in the same str_glue() command.\n\nTo display character quotes ’’, use single quotes within the surrounding double quotes (e.g. when providing date format - see example below).\n\nTip: You can use \\n to force a new line.\n\nTip: You use format() to adjust date display, and use Sys.Date() to display the current date.\n\nA simple example, of a dynamic plot caption:\n\nstr_glue(\"Data include {nrow(linelist)} cases and are current to {format(Sys.Date(), '%d %b %Y')}.\")\n\nData include 5888 cases and are current to 30 Sep 2024.\n\n\nAn alternative format is to use placeholders within the brackets and define the code in separate arguments at the end of the str_glue() function, as below. This can improve code readability if the text is long.\n\nstr_glue(\"Linelist as of {current_date}.\\nLast case hospitalized on {last_hospital}.\\n{n_missing_onset} cases are missing date of onset and not shown\",\n current_date = format(Sys.Date(), '%d %b %Y'),\n last_hospital = format(as.Date(max(linelist$date_hospitalisation, na.rm=T)), '%d %b %Y'),\n n_missing_onset = nrow(linelist %>% filter(is.na(date_onset)))\n )\n\nLinelist as of 30 Sep 2024.\nLast case hospitalized on 30 Apr 2015.\n256 cases are missing date of onset and not shown\n\n\nPulling from a data frame\nSometimes, it is useful to pull data from a data frame and have it pasted together in sequence. Below is an example data frame. We will use it to to make a summary statement about the jurisdictions and the new and total case counts.\n\n# make case data frame\ncase_table <- data.frame(\n zone = c(\"Zone 1\", \"Zone 2\", \"Zone 3\", \"Zone 4\", \"Zone 5\"),\n new_cases = c(3, 0, 7, 0, 15),\n total_cases = c(40, 4, 25, 10, 103)\n )\n\n\n\n\n\n\n\nUse str_glue_data(), which is specially made for taking data from data frame rows:\n\ncase_table %>% \n str_glue_data(\"{zone}: {new_cases} ({total_cases} total cases)\")\n\nZone 1: 3 (40 total cases)\nZone 2: 0 (4 total cases)\nZone 3: 7 (25 total cases)\nZone 4: 0 (10 total cases)\nZone 5: 15 (103 total cases)\n\n\nCombine strings across rows\nIf you are trying to “roll-up” values in a data frame column, e.g. combine values from multiple rows into just one row by pasting them together with a separator, see the section of the De-duplication page on “rolling-up” values.\nData frame to one line\nYou can make the statement appear in one line using str_c() (specifying the data frame and column names), and providing sep = and collapse = arguments.\n\nstr_c(case_table$zone, case_table$new_cases, sep = \" = \", collapse = \"; \")\n\n[1] \"Zone 1 = 3; Zone 2 = 0; Zone 3 = 7; Zone 4 = 0; Zone 5 = 15\"\n\n\nYou could add the pre-fix text “New Cases:” to the beginning of the statement by wrapping with a separate str_c() (if “New Cases:” was within the original str_c() it would appear multiple times).\n\nstr_c(\"New Cases: \", str_c(case_table$zone, case_table$new_cases, sep = \" = \", collapse = \"; \"))\n\n[1] \"New Cases: Zone 1 = 3; Zone 2 = 0; Zone 3 = 7; Zone 4 = 0; Zone 5 = 15\"\n\n\n\n\nUnite columns\nWithin a data frame, bringing together character values from multiple columns can be achieved with unite() from tidyr. This is the opposite of separate().\nProvide the name of the new united column. Then provide the names of the columns you wish to unite.\n\nBy default, the separator used in the united column is underscore _, but this can be changed with the sep = argument.\n\nremove = removes the input columns from the data frame (TRUE by default).\n\nna.rm = removes missing values while uniting (FALSE by default).\n\nBelow, we define a mini-data frame to demonstrate with:\n\ndf <- data.frame(\n case_ID = c(1:6),\n symptoms = c(\"jaundice, fever, chills\", # patient 1\n \"chills, aches, pains\", # patient 2 \n \"fever\", # patient 3\n \"vomiting, diarrhoea\", # patient 4\n \"bleeding from gums, fever\", # patient 5\n \"rapid pulse, headache\"), # patient 6\n outcome = c(\"Recover\", \"Death\", \"Death\", \"Recover\", \"Recover\", \"Recover\"))\n\n\ndf_split <- separate(df, symptoms, into = c(\"sym_1\", \"sym_2\", \"sym_3\"), extra = \"merge\")\n\nWarning: Expected 3 pieces. Missing pieces filled with `NA` in 2 rows [3, 4].\n\n\nHere is the example data frame:\n\n\n\n\n\n\nBelow, we unite the three symptom columns:\n\ndf_split %>% \n unite(\n col = \"all_symptoms\", # name of the new united column\n c(\"sym_1\", \"sym_2\", \"sym_3\"), # columns to unite\n sep = \", \", # separator to use in united column\n remove = TRUE, # if TRUE, removes input cols from the data frame\n na.rm = TRUE # if TRUE, missing values are removed before uniting\n )\n\n case_ID all_symptoms outcome\n1 1 jaundice, fever, chills Recover\n2 2 chills, aches, pains Death\n3 3 fever Death\n4 4 vomiting, diarrhoea Recover\n5 5 bleeding, from, gums, fever Recover\n6 6 rapid, pulse, headache Recover\n\n\n\n\n\nSplit\nTo split a string based on a pattern, use str_split(). It evaluates the string(s) and returns a list of character vectors consisting of the newly-split values.\nThe simple example below evaluates one string and splits it into three. By default it returns an object of class list with one element (a character vector) for each string initially provided. If simplify = TRUE it returns a character matrix.\nIn this example, one string is provided, and the function returns a list with one element - a character vector with three values.\n\nstr_split(string = \"jaundice, fever, chills\",\n pattern = \",\")\n\n[[1]]\n[1] \"jaundice\" \" fever\" \" chills\" \n\n\nIf the output is saved, you can then access the nth split value with bracket syntax. To access a specific value you can use syntax like this: the_returned_object[[1]][2], which would access the second value from the first evaluated string (“fever”). See the R basics page for more detail on accessing elements.\n\npt1_symptoms <- str_split(\"jaundice, fever, chills\", \",\")\n\npt1_symptoms[[1]][2] # extracts 2nd value from 1st (and only) element of the list\n\n[1] \" fever\"\n\n\nIf multiple strings are provided by str_split(), there will be more than one element in the returned list.\n\nsymptoms <- c(\"jaundice, fever, chills\", # patient 1\n \"chills, aches, pains\", # patient 2 \n \"fever\", # patient 3\n \"vomiting, diarrhoea\", # patient 4\n \"bleeding from gums, fever\", # patient 5\n \"rapid pulse, headache\") # patient 6\n\nstr_split(symptoms, \",\") # split each patient's symptoms\n\n[[1]]\n[1] \"jaundice\" \" fever\" \" chills\" \n\n[[2]]\n[1] \"chills\" \" aches\" \" pains\"\n\n[[3]]\n[1] \"fever\"\n\n[[4]]\n[1] \"vomiting\" \" diarrhoea\"\n\n[[5]]\n[1] \"bleeding from gums\" \" fever\" \n\n[[6]]\n[1] \"rapid pulse\" \" headache\" \n\n\nTo return a “character matrix” instead, which may be useful if creating data frame columns, set the argument simplify = TRUE as shown below:\n\nstr_split(symptoms, \",\", simplify = TRUE)\n\n [,1] [,2] [,3] \n[1,] \"jaundice\" \" fever\" \" chills\"\n[2,] \"chills\" \" aches\" \" pains\" \n[3,] \"fever\" \"\" \"\" \n[4,] \"vomiting\" \" diarrhoea\" \"\" \n[5,] \"bleeding from gums\" \" fever\" \"\" \n[6,] \"rapid pulse\" \" headache\" \"\" \n\n\nYou can also adjust the number of splits to create with the n = argument. For example, this restricts the number of splits to 2. Any further commas remain within the second values.\n\nstr_split(symptoms, \",\", simplify = TRUE, n = 2)\n\n [,1] [,2] \n[1,] \"jaundice\" \" fever, chills\"\n[2,] \"chills\" \" aches, pains\" \n[3,] \"fever\" \"\" \n[4,] \"vomiting\" \" diarrhoea\" \n[5,] \"bleeding from gums\" \" fever\" \n[6,] \"rapid pulse\" \" headache\" \n\n\nNote - the same outputs can be achieved with str_split_fixed(), in which you do not give the simplify argument, but must instead designate the number of columns (n).\n\nstr_split_fixed(symptoms, \",\", n = 2)\n\n\n\nSplit columns\nIf you are trying to split data frame column, it is best to use the separate() function from dplyr. It is used to split one character column into other columns.\nLet’s say we have a simple data frame df (defined and united in the unite section) containing a case_ID column, one character column with many symptoms, and one outcome column. Our goal is to separate the symptoms column into many columns - each one containing one symptom.\n\n\n\n\n\n\nAssuming the data are piped into separate(), first provide the column to be separated. Then provide into = as a vector c( ) containing the new columns names, as shown below.\n\nsep = the separator, can be a character, or a number (interpreted as the character position to split at).\nremove = FALSE by default, removes the input column.\n\nconvert = FALSE by default, will cause string “NA”s to become NA.\n\nextra = this controls what happens if there are more values created by the separation than new columns named.\n\nextra = \"warn\" means you will see a warning but it will drop excess values (the default).\n\nextra = \"drop\" means the excess values will be dropped with no warning.\n\nextra = \"merge\" will only split to the number of new columns listed in into - this setting will preserve all your data.\n\n\nAn example with extra = \"merge\" is below - no data is lost. Two new columns are defined but any third symptoms are left in the second new column:\n\n# third symptoms combined into second new column\ndf %>% \n separate(symptoms, into = c(\"sym_1\", \"sym_2\"), sep=\",\", extra = \"merge\")\n\nWarning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].\n\n\n case_ID sym_1 sym_2 outcome\n1 1 jaundice fever, chills Recover\n2 2 chills aches, pains Death\n3 3 fever <NA> Death\n4 4 vomiting diarrhoea Recover\n5 5 bleeding from gums fever Recover\n6 6 rapid pulse headache Recover\n\n\nWhen the default extra = \"drop\" is used below, a warning is given but the third symptoms are lost:\n\n# third symptoms are lost\ndf %>% \n separate(symptoms, into = c(\"sym_1\", \"sym_2\"), sep=\",\")\n\nWarning: Expected 2 pieces. Additional pieces discarded in 2 rows [1, 2].\n\n\nWarning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].\n\n\n case_ID sym_1 sym_2 outcome\n1 1 jaundice fever Recover\n2 2 chills aches Death\n3 3 fever <NA> Death\n4 4 vomiting diarrhoea Recover\n5 5 bleeding from gums fever Recover\n6 6 rapid pulse headache Recover\n\n\nCAUTION: If you do not provide enough into values for the new columns, your data may be truncated.\n\n\n\nArrange alphabetically\nSeveral strings can be sorted by alphabetical order. str_order() returns the order, while str_sort() returns the strings in that order.\n\n# strings\nhealth_zones <- c(\"Alba\", \"Takota\", \"Delta\")\n\n# return the alphabetical order\nstr_order(health_zones)\n\n[1] 1 3 2\n\n# return the strings in alphabetical order\nstr_sort(health_zones)\n\n[1] \"Alba\" \"Delta\" \"Takota\"\n\n\nTo use a different alphabet, add the argument locale =. See the full list of locales by entering stringi::stri_locale_list() in the R console.\n\n\n\nbase R functions\nIt is common to see base R functions paste() and paste0(), which concatenate vectors after converting all parts to character. They act similarly to str_c() but the syntax is arguably more complicated - in the parentheses each part is separated by a comma. The parts are either character text (in quotes) or pre-defined code objects (no quotes). For example:\n\nn_beds <- 10\nn_masks <- 20\n\npaste0(\"Regional hospital needs \", n_beds, \" beds and \", n_masks, \" masks.\")\n\n[1] \"Regional hospital needs 10 beds and 20 masks.\"\n\n\nsep = and collapse = arguments can be specified. paste() is simply paste0() with a default sep = \" \" (one space).", "crumbs": [ "Data Management", "10  Characters and strings" @@ -934,7 +934,7 @@ "href": "new_pages/characters_strings.html#handle-by-position", "title": "10  Characters and strings", "section": "10.4 Handle by position", - "text": "10.4 Handle by position\n\nExtract by character position\nUse str_sub() to return only a part of a string. The function takes three main arguments:\n\nthe character vector(s)\n\nstart position\n\nend position\n\nA few notes on position numbers:\n\nIf a position number is positive, the position is counted starting from the left end of the string.\n\nIf a position number is negative, it is counted starting from the right end of the string.\n\nPosition numbers are inclusive.\n\nPositions extending beyond the string will be truncated (removed).\n\nBelow are some examples applied to the string “pneumonia”:\n\n# start and end third from left (3rd letter from left)\nstr_sub(\"pneumonia\", 3, 3)\n\n[1] \"e\"\n\n# 0 is not present\nstr_sub(\"pneumonia\", 0, 0)\n\n[1] \"\"\n\n# 6th from left, to the 1st from right\nstr_sub(\"pneumonia\", 6, -1)\n\n[1] \"onia\"\n\n# 5th from right, to the 2nd from right\nstr_sub(\"pneumonia\", -5, -2)\n\n[1] \"moni\"\n\n# 4th from left to a position outside the string\nstr_sub(\"pneumonia\", 4, 15)\n\n[1] \"umonia\"\n\n\n\n\nExtract by word position\nTo extract the nth ‘word’, use word(), also from stringr. Provide the string(s), then the first word position to extract, and the last word position to extract.\nBy default, the separator between ‘words’ is assumed to be a space, unless otherwise indicated with sep = (e.g. sep = \"_\" when words are separated by underscores.\n\n# strings to evaluate\nchief_complaints <- c(\"I just got out of the hospital 2 days ago, but still can barely breathe.\",\n \"My stomach hurts\",\n \"Severe ear pain\")\n\n# extract 1st to 3rd words of each string\nword(chief_complaints, start = 1, end = 3, sep = \" \")\n\n[1] \"I just got\" \"My stomach hurts\" \"Severe ear pain\" \n\n\n\n\nReplace by character position\nstr_sub() paired with the assignment operator (<-) can be used to modify a part of a string:\n\nword <- \"pneumonia\"\n\n# convert the third and fourth characters to X \nstr_sub(word, 3, 4) <- \"XX\"\n\n# print\nword\n\n[1] \"pnXXmonia\"\n\n\nAn example applied to multiple strings (e.g. a column). Note the expansion in length of “HIV”.\n\nwords <- c(\"pneumonia\", \"tubercolosis\", \"HIV\")\n\n# convert the third and fourth characters to X \nstr_sub(words, 3, 4) <- \"XX\"\n\nwords\n\n[1] \"pnXXmonia\" \"tuXXrcolosis\" \"HIXX\" \n\n\n\n\nEvaluate length\n\nstr_length(\"abc\")\n\n[1] 3\n\n\nAlternatively, use nchar() from base R", + "text": "10.4 Handle by position\n\nExtract by character position\nUse str_sub() to return only a part of a string. The function takes three main arguments:\n\nthe character vector(s).\n\nstart position.\nend position.\n\nA few notes on position numbers:\n\nIf a position number is positive, the position is counted starting from the left end of the string.\n\nIf a position number is negative, it is counted starting from the right end of the string.\n\nPosition numbers are inclusive.\n\nPositions extending beyond the string will be truncated (removed).\n\nBelow are some examples applied to the string “pneumonia”:\n\n# start and end third from left (3rd letter from left)\nstr_sub(\"pneumonia\", 3, 3)\n\n[1] \"e\"\n\n# 0 is not present\nstr_sub(\"pneumonia\", 0, 0)\n\n[1] \"\"\n\n# 6th from left, to the 1st from right\nstr_sub(\"pneumonia\", 6, -1)\n\n[1] \"onia\"\n\n# 5th from right, to the 2nd from right\nstr_sub(\"pneumonia\", -5, -2)\n\n[1] \"moni\"\n\n# 4th from left to a position outside the string\nstr_sub(\"pneumonia\", 4, 15)\n\n[1] \"umonia\"\n\n\n\n\nExtract by word position\nTo extract the nth ‘word’, use word(), also from stringr. Provide the string(s), then the first word position to extract, and the last word position to extract.\nBy default, the separator between ‘words’ is assumed to be a space, unless otherwise indicated with sep = (e.g. sep = \"_\" when words are separated by underscores.\n\n# strings to evaluate\nchief_complaints <- c(\"I just got out of the hospital 2 days ago, but still can barely breathe.\",\n \"My stomach hurts\",\n \"Severe ear pain\")\n\n# extract 1st to 3rd words of each string\nword(chief_complaints, start = 1, end = 3, sep = \" \")\n\n[1] \"I just got\" \"My stomach hurts\" \"Severe ear pain\" \n\n\n\n\nReplace by character position\nstr_sub() paired with the assignment operator (<-) can be used to modify a part of a string:\n\nword <- \"pneumonia\"\n\n# convert the third and fourth characters to X \nstr_sub(word, 3, 4) <- \"XX\"\n\n# print\nword\n\n[1] \"pnXXmonia\"\n\n\nAn example applied to multiple strings (e.g. a column). Note the expansion in length of “HIV”.\n\nwords <- c(\"pneumonia\", \"tubercolosis\", \"HIV\")\n\n# convert the third and fourth characters to X \nstr_sub(words, 3, 4) <- \"XX\"\n\nwords\n\n[1] \"pnXXmonia\" \"tuXXrcolosis\" \"HIXX\" \n\n\n\n\nEvaluate length\n\nstr_length(\"abc\")\n\n[1] 3\n\n\nAlternatively, use nchar() from base R", "crumbs": [ "Data Management", "10  Characters and strings" @@ -945,7 +945,7 @@ "href": "new_pages/characters_strings.html#patterns", "title": "10  Characters and strings", "section": "10.5 Patterns", - "text": "10.5 Patterns\nMany stringr functions work to detect, locate, extract, match, replace, and split based on a specified pattern.\n\n\nDetect a pattern\nUse str_detect() as below to detect presence/absence of a pattern within a string. First provide the string or vector to search in (string =), and then the pattern to look for (pattern =). Note that by default the search is case sensitive!\n\nstr_detect(string = \"primary school teacher\", pattern = \"teach\")\n\n[1] TRUE\n\n\nThe argument negate = can be included and set to TRUE if you want to know if the pattern is NOT present.\n\nstr_detect(string = \"primary school teacher\", pattern = \"teach\", negate = TRUE)\n\n[1] FALSE\n\n\nTo ignore case/capitalization, wrap the pattern within regex(), and within regex() add the argument ignore_case = TRUE (or T as shorthand).\n\nstr_detect(string = \"Teacher\", pattern = regex(\"teach\", ignore_case = T))\n\n[1] TRUE\n\n\nWhen str_detect() is applied to a character vector or a data frame column, it will return TRUE or FALSE for each of the values.\n\n# a vector/column of occupations \noccupations <- c(\"field laborer\",\n \"university professor\",\n \"primary school teacher & tutor\",\n \"tutor\",\n \"nurse at regional hospital\",\n \"lineworker at Amberdeen Fish Factory\",\n \"physican\",\n \"cardiologist\",\n \"office worker\",\n \"food service\")\n\n# Detect presence of pattern \"teach\" in each string - output is vector of TRUE/FALSE\nstr_detect(occupations, \"teach\")\n\n [1] FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE\n\n\nIf you need to count the TRUEs, simply sum() the output. This counts the number TRUE.\n\nsum(str_detect(occupations, \"teach\"))\n\n[1] 1\n\n\nTo search inclusive of multiple terms, include them separated by OR bars (|) within the pattern = argument, as shown below:\n\nsum(str_detect(string = occupations, pattern = \"teach|professor|tutor\"))\n\n[1] 3\n\n\nIf you need to build a long list of search terms, you can combine them using str_c() and sep = |, then define this is a character object, and then reference the vector later more succinctly. The example below includes possible occupation search terms for front-line medical providers.\n\n# search terms\noccupation_med_frontline <- str_c(\"medical\", \"medicine\", \"hcw\", \"healthcare\", \"home care\", \"home health\",\n \"surgeon\", \"doctor\", \"doc\", \"physician\", \"surgery\", \"peds\", \"pediatrician\",\n \"intensivist\", \"cardiologist\", \"coroner\", \"nurse\", \"nursing\", \"rn\", \"lpn\",\n \"cna\", \"pa\", \"physician assistant\", \"mental health\",\n \"emergency department technician\", \"resp therapist\", \"respiratory\",\n \"phlebotomist\", \"pharmacy\", \"pharmacist\", \"hospital\", \"snf\", \"rehabilitation\",\n \"rehab\", \"activity\", \"elderly\", \"subacute\", \"sub acute\",\n \"clinic\", \"post acute\", \"therapist\", \"extended care\",\n \"dental\", \"dential\", \"dentist\", sep = \"|\")\n\noccupation_med_frontline\n\n[1] \"medical|medicine|hcw|healthcare|home care|home health|surgeon|doctor|doc|physician|surgery|peds|pediatrician|intensivist|cardiologist|coroner|nurse|nursing|rn|lpn|cna|pa|physician assistant|mental health|emergency department technician|resp therapist|respiratory|phlebotomist|pharmacy|pharmacist|hospital|snf|rehabilitation|rehab|activity|elderly|subacute|sub acute|clinic|post acute|therapist|extended care|dental|dential|dentist\"\n\n\nThis command returns the number of occupations which contain any one of the search terms for front-line medical providers (occupation_med_frontline):\n\nsum(str_detect(string = occupations, pattern = occupation_med_frontline))\n\n[1] 2\n\n\nBase R string search functions\nThe base function grepl() works similarly to str_detect(), in that it searches for matches to a pattern and returns a logical vector. The basic syntax is grepl(pattern, strings_to_search, ignore.case = FALSE, ...). One advantage is that the ignore.case argument is easier to write (there is no need to involve the regex() function).\nLikewise, the base functions sub() and gsub() act similarly to str_replace(). Their basic syntax is: gsub(pattern, replacement, strings_to_search, ignore.case = FALSE). sub() will replace the first instance of the pattern, whereas gsub() will replace all instances of the pattern.\n\nConvert commas to periods\nHere is an example of using gsub() to convert commas to periods in a vector of numbers. This could be useful if your data come from parts of the world other than the United States or Great Britain.\nThe inner gsub() which acts first on lengths is converting any periods to no space ““. The period character”.” has to be “escaped” with two slashes to actually signify a period, because “.” in regex means “any character”. Then, the result (with only commas) is passed to the outer gsub() in which commas are replaced by periods.\n\nlengths <- c(\"2.454,56\", \"1,2\", \"6.096,5\")\n\nas.numeric(gsub(pattern = \",\", # find commas \n replacement = \".\", # replace with periods\n x = gsub(\"\\\\.\", \"\", lengths) # vector with other periods removed (periods escaped)\n )\n ) # convert outcome to numeric\n\n\n\n\nReplace all\nUse str_replace_all() as a “find and replace” tool. First, provide the strings to be evaluated to string =, then the pattern to be replaced to pattern =, and then the replacement value to replacement =. The example below replaces all instances of “dead” with “deceased”. Note, this IS case sensitive.\n\noutcome <- c(\"Karl: dead\",\n \"Samantha: dead\",\n \"Marco: not dead\")\n\nstr_replace_all(string = outcome, pattern = \"dead\", replacement = \"deceased\")\n\n[1] \"Karl: deceased\" \"Samantha: deceased\" \"Marco: not deceased\"\n\n\nNotes:\n\nTo replace a pattern with NA, use str_replace_na().\n\nThe function str_replace() replaces only the first instance of the pattern within each evaluated string.\n\n\n\n\nDetect within logic\nWithin case_when()\nstr_detect() is often used within case_when() (from dplyr). Let’s say occupations is a column in the linelist. The mutate() below creates a new column called is_educator by using conditional logic via case_when(). See the page on data cleaning to learn more about case_when().\n\ndf <- df %>% \n mutate(is_educator = case_when(\n # term search within occupation, not case sensitive\n str_detect(occupations,\n regex(\"teach|prof|tutor|university\",\n ignore_case = TRUE)) ~ \"Educator\",\n # all others\n TRUE ~ \"Not an educator\"))\n\nAs a reminder, it may be important to add exclusion criteria to the conditional logic (negate = F):\n\ndf <- df %>% \n # value in new column is_educator is based on conditional logic\n mutate(is_educator = case_when(\n \n # occupation column must meet 2 criteria to be assigned \"Educator\":\n # it must have a search term AND NOT any exclusion term\n \n # Must have a search term\n str_detect(occupations,\n regex(\"teach|prof|tutor|university\", ignore_case = T)) & \n \n # AND must NOT have an exclusion term\n str_detect(occupations,\n regex(\"admin\", ignore_case = T),\n negate = TRUE ~ \"Educator\"\n \n # All rows not meeting above criteria\n TRUE ~ \"Not an educator\"))\n\n\n\n\nLocate pattern position\nTo locate the first position of a pattern, use str_locate(). It outputs a start and end position.\n\nstr_locate(\"I wish\", \"sh\")\n\n start end\n[1,] 5 6\n\n\nLike other str functions, there is an “_all” version (str_locate_all()) which will return the positions of all instances of the pattern within each string. This outputs as a list.\n\nphrases <- c(\"I wish\", \"I hope\", \"he hopes\", \"He hopes\")\n\nstr_locate(phrases, \"h\" ) # position of *first* instance of the pattern\n\n start end\n[1,] 6 6\n[2,] 3 3\n[3,] 1 1\n[4,] 4 4\n\nstr_locate_all(phrases, \"h\" ) # position of *every* instance of the pattern\n\n[[1]]\n start end\n[1,] 6 6\n\n[[2]]\n start end\n[1,] 3 3\n\n[[3]]\n start end\n[1,] 1 1\n[2,] 4 4\n\n[[4]]\n start end\n[1,] 4 4\n\n\n\n\n\nExtract a match\nstr_extract_all() returns the matching patterns themselves, which is most useful when you have offered several patterns via “OR” conditions. For example, looking in the string vector of occupations (see previous tab) for either “teach”, “prof”, or “tutor”.\nstr_extract_all() returns a list which contains all matches for each evaluated string. See below how occupation 3 has two pattern matches within it.\n\nstr_extract_all(occupations, \"teach|prof|tutor\")\n\n[[1]]\ncharacter(0)\n\n[[2]]\n[1] \"prof\"\n\n[[3]]\n[1] \"teach\" \"tutor\"\n\n[[4]]\n[1] \"tutor\"\n\n[[5]]\ncharacter(0)\n\n[[6]]\ncharacter(0)\n\n[[7]]\ncharacter(0)\n\n[[8]]\ncharacter(0)\n\n[[9]]\ncharacter(0)\n\n[[10]]\ncharacter(0)\n\n\nstr_extract() extracts only the first match in each evaluated string, producing a character vector with one element for each evaluated string. It returns NA where there was no match. The NAs can be removed by wrapping the returned vector with na.exclude(). Note how the second of occupation 3’s matches is not shown.\n\nstr_extract(occupations, \"teach|prof|tutor\")\n\n [1] NA \"prof\" \"teach\" \"tutor\" NA NA NA NA NA \n[10] NA \n\n\n\n\n\nSubset and count\nAligned functions include str_subset() and str_count().\nstr_subset() returns the actual values which contained the pattern:\n\nstr_subset(occupations, \"teach|prof|tutor\")\n\n[1] \"university professor\" \"primary school teacher & tutor\"\n[3] \"tutor\" \n\n\nstr_count() returns a vector of numbers: the number of times a search term appears in each evaluated value.\n\nstr_count(occupations, regex(\"teach|prof|tutor\", ignore_case = TRUE))\n\n [1] 0 1 2 1 0 0 0 0 0 0\n\n\n\n\n\nRegex groups\nUNDER CONSTRUCTION", + "text": "10.5 Patterns\nMany stringr functions work to detect, locate, extract, match, replace, and split based on a specified pattern.\n\n\nDetect a pattern\nUse str_detect() as below to detect presence/absence of a pattern within a string. First provide the string or vector to search in (string =), and then the pattern to look for (pattern =). Note that by default the search is case sensitive!\n\nstr_detect(string = \"primary school teacher\", pattern = \"teach\")\n\n[1] TRUE\n\n\nThe argument negate = can be included and set to TRUE if you want to know if the pattern is NOT present.\n\nstr_detect(string = \"primary school teacher\", pattern = \"teach\", negate = TRUE)\n\n[1] FALSE\n\n\nTo ignore case/capitalization, wrap the pattern within regex(), and within regex() add the argument ignore_case = TRUE (or T as shorthand).\n\nstr_detect(string = \"Teacher\", pattern = regex(\"teach\", ignore_case = T))\n\n[1] TRUE\n\n\nWhen str_detect() is applied to a character vector or a data frame column, it will return TRUE or FALSE for each of the values.\n\n# a vector/column of occupations \noccupations <- c(\"field laborer\",\n \"university professor\",\n \"primary school teacher & tutor\",\n \"tutor\",\n \"nurse at regional hospital\",\n \"lineworker at Amberdeen Fish Factory\",\n \"physican\",\n \"cardiologist\",\n \"office worker\",\n \"food service\")\n\n# Detect presence of pattern \"teach\" in each string - output is vector of TRUE/FALSE\nstr_detect(occupations, \"teach\")\n\n [1] FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE\n\n\nIf you need to count the TRUEs, simply sum() the output. This counts the number TRUE.\n\nsum(str_detect(occupations, \"teach\"))\n\n[1] 1\n\n\nTo search inclusive of multiple terms, include them separated by OR bars (|) within the pattern = argument, as shown below:\n\nsum(str_detect(string = occupations, pattern = \"teach|professor|tutor\"))\n\n[1] 3\n\n\nIf you need to build a long list of search terms, you can combine them using str_c() and sep = |, then define this is a character object, and then reference the vector later more succinctly. The example below includes possible occupation search terms for front-line medical providers.\n\n# search terms\noccupation_med_frontline <- str_c(\"medical\", \"medicine\", \"hcw\", \"healthcare\", \"home care\", \"home health\",\n \"surgeon\", \"doctor\", \"doc\", \"physician\", \"surgery\", \"peds\", \"pediatrician\",\n \"intensivist\", \"cardiologist\", \"coroner\", \"nurse\", \"nursing\", \"rn\", \"lpn\",\n \"cna\", \"pa\", \"physician assistant\", \"mental health\",\n \"emergency department technician\", \"resp therapist\", \"respiratory\",\n \"phlebotomist\", \"pharmacy\", \"pharmacist\", \"hospital\", \"snf\", \"rehabilitation\",\n \"rehab\", \"activity\", \"elderly\", \"subacute\", \"sub acute\",\n \"clinic\", \"post acute\", \"therapist\", \"extended care\",\n \"dental\", \"dential\", \"dentist\", sep = \"|\")\n\noccupation_med_frontline\n\n[1] \"medical|medicine|hcw|healthcare|home care|home health|surgeon|doctor|doc|physician|surgery|peds|pediatrician|intensivist|cardiologist|coroner|nurse|nursing|rn|lpn|cna|pa|physician assistant|mental health|emergency department technician|resp therapist|respiratory|phlebotomist|pharmacy|pharmacist|hospital|snf|rehabilitation|rehab|activity|elderly|subacute|sub acute|clinic|post acute|therapist|extended care|dental|dential|dentist\"\n\n\nThis command returns the number of occupations which contain any one of the search terms for front-line medical providers (occupation_med_frontline):\n\nsum(str_detect(string = occupations, pattern = occupation_med_frontline))\n\n[1] 2\n\n\nBase R string search functions\nThe base function grepl() works similarly to str_detect(), in that it searches for matches to a pattern and returns a logical vector. The basic syntax is grepl(pattern, strings_to_search, ignore.case = FALSE, ...). One advantage is that the ignore.case argument is easier to write (there is no need to involve the regex() function).\nLikewise, the base functions sub() and gsub() act similarly to str_replace(). Their basic syntax is: gsub(pattern, replacement, strings_to_search, ignore.case = FALSE). sub() will replace the first instance of the pattern, whereas gsub() will replace all instances of the pattern.\n\nConvert commas to periods\nHere is an example of using gsub() to convert commas to periods in a vector of numbers. This could be useful if your data come from parts of the world other than the United States or Great Britain.\nThe inner gsub() which acts first on lengths is converting any periods to no space ““. The period character”.” has to be “escaped” with two slashes to actually signify a period, because “.” in regex means “any character”. Then, the result (with only commas) is passed to the outer gsub() in which commas are replaced by periods.\n\nlengths <- c(\"2.454,56\", \"1,2\", \"6.096,5\")\n\nas.numeric(gsub(pattern = \",\", # find commas \n replacement = \".\", # replace with periods\n x = gsub(\"\\\\.\", \"\", lengths) # vector with other periods removed (periods escaped)\n )\n ) # convert outcome to numeric\n\n\n\n\nReplace all\nUse str_replace_all() as a “find and replace” tool. First, provide the strings to be evaluated to string =, then the pattern to be replaced to pattern =, and then the replacement value to replacement =. The example below replaces all instances of “dead” with “deceased”. Note, this IS case sensitive.\n\noutcome <- c(\"Karl: dead\",\n \"Samantha: dead\",\n \"Marco: not dead\")\n\nstr_replace_all(string = outcome, pattern = \"dead\", replacement = \"deceased\")\n\n[1] \"Karl: deceased\" \"Samantha: deceased\" \"Marco: not deceased\"\n\n\nNotes:\n\nTo replace a pattern with NA, use str_replace_na().\n\nThe function str_replace() replaces only the first instance of the pattern within each evaluated string.\n\n\n\n\nDetect within logic\nWithin case_when()\nstr_detect() is often used within case_when() (from dplyr). Let’s say occupations is a column in the linelist. The mutate() below creates a new column called is_educator by using conditional logic via case_when(). See the page on data cleaning to learn more about case_when().\n\ndf <- df %>% \n mutate(is_educator = case_when(\n # term search within occupation, not case sensitive\n str_detect(occupations,\n regex(\"teach|prof|tutor|university\",\n ignore_case = TRUE)) ~ \"Educator\",\n # all others\n TRUE ~ \"Not an educator\"))\n\nAs a reminder, it may be important to add exclusion criteria to the conditional logic (negate = F):\n\ndf <- df %>% \n # value in new column is_educator is based on conditional logic\n mutate(is_educator = case_when(\n \n # occupation column must meet 2 criteria to be assigned \"Educator\":\n # it must have a search term AND NOT any exclusion term\n \n # Must have a search term\n str_detect(occupations,\n regex(\"teach|prof|tutor|university\", ignore_case = T)) & \n \n # AND must NOT have an exclusion term\n str_detect(occupations,\n regex(\"admin\", ignore_case = T),\n negate = TRUE ~ \"Educator\"\n \n # All rows not meeting above criteria\n TRUE ~ \"Not an educator\"))\n\n\n\n\nLocate pattern position\nTo locate the first position of a pattern, use str_locate(). It outputs a start and end position.\n\nstr_locate(\"I wish\", \"sh\")\n\n start end\n[1,] 5 6\n\n\nLike other str functions, there is an “_all” version (str_locate_all()) which will return the positions of all instances of the pattern within each string. This outputs as a list.\n\nphrases <- c(\"I wish\", \"I hope\", \"he hopes\", \"He hopes\")\n\nstr_locate(phrases, \"h\" ) # position of *first* instance of the pattern\n\n start end\n[1,] 6 6\n[2,] 3 3\n[3,] 1 1\n[4,] 4 4\n\nstr_locate_all(phrases, \"h\" ) # position of *every* instance of the pattern\n\n[[1]]\n start end\n[1,] 6 6\n\n[[2]]\n start end\n[1,] 3 3\n\n[[3]]\n start end\n[1,] 1 1\n[2,] 4 4\n\n[[4]]\n start end\n[1,] 4 4\n\n\n\n\n\nExtract a match\nstr_extract_all() returns the matching patterns themselves, which is most useful when you have offered several patterns via “OR” conditions. For example, looking in the string vector of occupations (see previous tab) for either “teach”, “prof”, or “tutor”.\nstr_extract_all() returns a list which contains all matches for each evaluated string. See below how occupation 3 has two pattern matches within it.\n\nstr_extract_all(occupations, \"teach|prof|tutor\")\n\n[[1]]\ncharacter(0)\n\n[[2]]\n[1] \"prof\"\n\n[[3]]\n[1] \"teach\" \"tutor\"\n\n[[4]]\n[1] \"tutor\"\n\n[[5]]\ncharacter(0)\n\n[[6]]\ncharacter(0)\n\n[[7]]\ncharacter(0)\n\n[[8]]\ncharacter(0)\n\n[[9]]\ncharacter(0)\n\n[[10]]\ncharacter(0)\n\n\nstr_extract() extracts only the first match in each evaluated string, producing a character vector with one element for each evaluated string. It returns NA where there was no match. The NAs can be removed by wrapping the returned vector with na.exclude(). Note how the second of occupation 3’s matches is not shown.\n\nstr_extract(occupations, \"teach|prof|tutor\")\n\n [1] NA \"prof\" \"teach\" \"tutor\" NA NA NA NA NA \n[10] NA \n\n\n\n\n\nSubset and count\nAligned functions include str_subset() and str_count().\nstr_subset() returns the actual values which contained the pattern:\n\nstr_subset(occupations, \"teach|prof|tutor\")\n\n[1] \"university professor\" \"primary school teacher & tutor\"\n[3] \"tutor\" \n\n\nstr_count() returns a vector of numbers: the number of times a search term appears in each evaluated value.\n\nstr_count(occupations, regex(\"teach|prof|tutor\", ignore_case = TRUE))\n\n [1] 0 1 2 1 0 0 0 0 0 0", "crumbs": [ "Data Management", "10  Characters and strings" @@ -988,8 +988,8 @@ "objectID": "new_pages/characters_strings.html#resources", "href": "new_pages/characters_strings.html#resources", "title": "10  Characters and strings", - "section": "10.9 Resources", - "text": "10.9 Resources\nA reference sheet for stringr functions can be found here\nA vignette on stringr can be found here", + "section": "10.8 Resources", + "text": "10.8 Resources\nA reference sheet for stringr functions can be found here\nA vignette on stringr can be found here", "crumbs": [ "Data Management", "10  Characters and strings" @@ -1011,7 +1011,7 @@ "href": "new_pages/factors.html#preparation", "title": "11  Factors", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export\n here, # filepaths\n lubridate, # working with dates\n forcats, # factors\n aweek, # create epiweeks with automatic factor levels\n janitor, # tables\n tidyverse # data mgmt and viz\n )\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n# import your dataset\nlinelist <- import(\"linelist_cleaned.rds\")\n\n\n\nNew categorical variable\nFor demonstration in this page we will use a common scenario - the creation of a new categorical variable.\nNote that if you convert a numeric column to class factor, you will not be able to calculate numeric statistics on it.\n\nCreate column\nWe use the existing column days_onset_hosp (days from symptom onset to hospital admission) and create a new column delay_cat by classifying each row into one of several categories. We do this with the dplyr function case_when(), which sequentially applies logical criteria (right-side) to each row and returns the corresponding left-side value for the new column delay_cat. Read more about case_when() in Cleaning data and core functions.\n\nlinelist <- linelist %>% \n mutate(delay_cat = case_when(\n # criteria # new value if TRUE\n days_onset_hosp < 2 ~ \"<2 days\",\n days_onset_hosp >= 2 & days_onset_hosp < 5 ~ \"2-5 days\",\n days_onset_hosp >= 5 ~ \">5 days\",\n is.na(days_onset_hosp) ~ NA_character_,\n TRUE ~ \"Check me\")) \n\n\n\nDefault value order\nAs created with case_when(), the new column delay_cat is a categorical column of class Character - not yet a factor. Thus, in a frequency table, we see that the unique values appear in a default alpha-numeric order - an order that does not make much intuitive sense:\n\ntable(linelist$delay_cat, useNA = \"always\")\n\n\n <2 days >5 days 2-5 days <NA> \n 2990 602 2040 256 \n\n\nLikewise, if we make a bar plot, the values also appear in this order on the x-axis (see the ggplot basics page for more on ggplot2 - the most common visualization package in R).\n\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = delay_cat))", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export\n here, # filepaths\n lubridate, # working with dates\n forcats, # factors\n aweek, # create epiweeks with automatic factor levels\n janitor, # tables\n tidyverse # data mgmt and viz\n )\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n# import your dataset\nlinelist <- import(\"linelist_cleaned.rds\")\n\n\n\nNew categorical variable\nFor demonstration in this page we will use a common scenario - the creation of a new categorical variable.\nNote that if you convert a numeric column to class factor, you will not be able to calculate numeric statistics on it.\n\nCreate column\nWe use the existing column days_onset_hosp (days from symptom onset to hospital admission) and create a new column delay_cat by classifying each row into one of several categories. We do this with the dplyr function case_when(), which sequentially applies logical criteria (right-side) to each row and returns the corresponding left-side value for the new column delay_cat. Read more about case_when() in Cleaning data and core functions.\n\nlinelist <- linelist %>% \n mutate(delay_cat = case_when(\n # criteria # new value if TRUE\n days_onset_hosp < 2 ~ \"<2 days\",\n days_onset_hosp >= 2 & days_onset_hosp < 5 ~ \"2-5 days\",\n days_onset_hosp >= 5 ~ \">5 days\",\n is.na(days_onset_hosp) ~ NA_character_,\n TRUE ~ \"Check me\")) \n\n\n\nDefault value order\nAs created with case_when(), the new column delay_cat is a categorical column of class Character - not yet a factor. Thus, in a frequency table, we see that the unique values appear in a default alpha-numeric order - an order that does not make much intuitive sense:\n\ntable(linelist$delay_cat, useNA = \"always\")\n\n\n <2 days >5 days 2-5 days <NA> \n 2990 602 2040 256 \n\n\nLikewise, if we make a bar plot, the values also appear in this order on the x-axis (see the ggplot basics page for more on ggplot2 - the most common visualization package in R).\n\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = delay_cat))", "crumbs": [ "Data Management", "11  Factors" @@ -1022,7 +1022,7 @@ "href": "new_pages/factors.html#convert-to-factor", "title": "11  Factors", "section": "11.2 Convert to factor", - "text": "11.2 Convert to factor\nTo convert a character or numeric column to class factor, you can use any function from the forcats package (many are detailed below). They will convert to class factor and then also perform or allow certain ordering of the levels - for example using fct_relevel() lets you manually specify the level order. The function as_factor() simply converts the class without any further capabilities.\nThe base R function factor() converts a column to factor and allows you to manually specify the order of the levels, as a character vector to its levels = argument.\nBelow we use mutate() and fct_relevel() to convert the column delay_cat from class character to class factor. The column delay_cat is created in the Preparation section above.\n\nlinelist <- linelist %>%\n mutate(delay_cat = fct_relevel(delay_cat))\n\nThe unique “values” in this column are now considered “levels” of the factor. The levels have an order, which can be printed with the base R function levels(), or alternatively viewed in a count table via table() from base R or tabyl() from janitor. By default, the order of the levels will be alpha-numeric, as before. Note that NA is not a factor level.\n\nlevels(linelist$delay_cat)\n\n[1] \"<2 days\" \">5 days\" \"2-5 days\"\n\n\nThe function fct_relevel() has the additional utility of allowing you to manually specify the level order. Simply write the level values in order, in quotation marks, separated by commas, as shown below. Note that the spelling must exactly match the values. If you want to create levels that do not exist in the data, use fct_expand() instead).\n\nlinelist <- linelist %>%\n mutate(delay_cat = fct_relevel(delay_cat, \"<2 days\", \"2-5 days\", \">5 days\"))\n\nWe can now see that the levels are ordered, as specified in the previous command, in a sensible order.\n\nlevels(linelist$delay_cat)\n\n[1] \"<2 days\" \"2-5 days\" \">5 days\" \n\n\nNow the plot order makes more intuitive sense as well.\n\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = delay_cat))", + "text": "11.2 Convert to factor\nTo convert a character or numeric column to class factor, you can use any function from the forcats package (many are detailed below). They will convert to class factor and then also perform or allow certain ordering of the levels - for example using fct_relevel() lets you manually specify the level order. The function as_factor() simply converts the class without any further capabilities.\nThe base R function factor() converts a column to factor and allows you to manually specify the order of the levels, as a character vector to its levels = argument.\nBelow we use mutate() and fct_relevel() to convert the column delay_cat from class character to class factor. The column delay_cat is created in the Preparation section above.\n\nlinelist <- linelist %>%\n mutate(delay_cat = fct_relevel(delay_cat))\n\nThe unique “values” in this column are now considered “levels” of the factor. The levels have an order, which can be printed with the base R function levels(), or alternatively viewed in a count table via table() from base R or tabyl() from janitor. By default, the order of the levels will be alpha-numeric, as before. Note that NA is not a factor level.\n\nlevels(linelist$delay_cat)\n\n[1] \"<2 days\" \">5 days\" \"2-5 days\"\n\n\nThe function fct_relevel() has the additional utility of allowing you to manually specify the level order. Simply write the level values in order, in quotation marks, separated by commas, as shown below. Note that the spelling must exactly match the values. If you want to create levels that do not exist in the data, use fct_expand() instead.\n\nlinelist <- linelist %>%\n mutate(delay_cat = fct_relevel(delay_cat, \"<2 days\", \"2-5 days\", \">5 days\"))\n\nWe can now see that the levels are ordered, as specified in the previous command, in a sensible order.\n\nlevels(linelist$delay_cat)\n\n[1] \"<2 days\" \"2-5 days\" \">5 days\" \n\n\nNow the plot order makes more intuitive sense as well.\n\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = delay_cat))", "crumbs": [ "Data Management", "11  Factors" @@ -1044,7 +1044,7 @@ "href": "new_pages/factors.html#fct_adjust", "title": "11  Factors", "section": "11.4 Adjust level order", - "text": "11.4 Adjust level order\nThe package forcats offers useful functions to easily adjust the order of a factor’s levels (after a column been defined as class factor):\nThese functions can be applied to a factor column in two contexts:\n\nTo the column in the data frame, as usual, so the transformation is available for any subsequent use of the data\n\nInside of a plot, so that the change is applied only within the plot\n\n\nManually\nThis function is used to manually order the factor levels. If used on a non-factor column, the column will first be converted to class factor.\nWithin the parentheses first provide the factor column name, then provide either:\n\nAll the levels in the desired order (as a character vector c()), or\n\nOne level and it’s corrected placement using the after = argument\n\nHere is an example of redefining the column delay_cat (which is already class Factor) and specifying all the desired order of levels.\n\n# re-define level order\nlinelist <- linelist %>% \n mutate(delay_cat = fct_relevel(delay_cat, c(\"<2 days\", \"2-5 days\", \">5 days\")))\n\nIf you only want to move one level, you can specify it to fct_relevel() alone and give a number to the after = argument to indicate where in the order it should be. For example, the command below shifts “<2 days” to the second position:\n\n# re-define level order\nlinelist %>% \n mutate(delay_cat = fct_relevel(delay_cat, \"<2 days\", after = 1)) %>% \n tabyl(delay_cat)\n\n\n\nWithin a plot\nThe forcats commands can be used to set the level order in the data frame, or only within a plot. By using the command to “wrap around” the column name within the ggplot() plotting command, you can reverse/relevel/etc. the transformation will only apply within that plot.\nBelow, two plots are created with ggplot() (see the ggplot basics page). In the first, the delay_cat column is mapped to the x-axis of the plot, with it’s default level order as in the data linelist. In the second example it is wrapped within fct_relevel() and the order is changed in the plot.\n\n# Alpha-numeric default order - no adjustment within ggplot\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = delay_cat))\n\n# Factor level order adjusted within ggplot\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = fct_relevel(delay_cat, c(\"<2 days\", \"2-5 days\", \">5 days\"))))\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNote that default x-axis title is now quite complicated - you can overwrite this title with the ggplot2 labs() argument.\n\n\nReverse\nIt is rather common that you want to reverse the level order. Simply wrap the factor with fct_rev().\nNote that if you want to reverse only a plot legend but not the actual factor levels, you can do that with guides() (see ggplot tips).\n\n\nBy frequency\nTo order by frequency that the value appears in the data, use fct_infreq(). Any missing values (NA) will automatically be included at the end, unless they are converted to an explicit level (see this section). You can reverse the order by further wrapping with fct_rev().\nThis function can be used within a ggplot(), as shown below.\n\n# ordered by frequency\nggplot(data = linelist, aes(x = fct_infreq(delay_cat)))+\n geom_bar()+\n labs(x = \"Delay onset to admission (days)\",\n title = \"Ordered by frequency\")\n\n# reversed frequency\nggplot(data = linelist, aes(x = fct_rev(fct_infreq(delay_cat))))+\n geom_bar()+\n labs(x = \"Delay onset to admission (days)\",\n title = \"Reverse of order by frequency\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nBy appearance\nUse fct_inorder() to set the level order to match the order of appearance in the data, starting from the first row. This can be useful if you first carefully arrange() the data in the data frame, and then use this to set the factor order.\n\n\nBy summary statistic of another column\nYou can use fct_reorder() to order the levels of one column by a summary statistic of another column. Visually, this can result in pleasing plots where the bars/points ascend or descend steadily across the plot.\nIn the examples below, the x-axis is delay_cat, and the y-axis is numeric column ct_blood (cycle-threshold value). Box plots show the CT value distribution by delay_cat group. We want to order the box plots in ascending order by the group median CT value.\nIn the first example below, the default order alpha-numeric level order is used. You can see the box plot heights are jumbled and not in any particular order. In the second example, the delay_cat column (mapped to the x-axis) has been wrapped in fct_reorder(), the column ct_blood is given as the second argument, and “median” is given as the third argument (you could also use “max”, “mean”, “min”, etc). Thus, the order of the levels of delay_cat will now reflect ascending median CT values of each delay_cat group’s median CT value. This is reflected in the second plot - the box plots have been re-arranged to ascend. Note how NA (missing) will appear at the end, unless converted to an explicit level.\n\n# boxplots ordered by original factor levels\nggplot(data = linelist)+\n geom_boxplot(\n aes(x = delay_cat,\n y = ct_blood, \n fill = delay_cat))+\n labs(x = \"Delay onset to admission (days)\",\n title = \"Ordered by original alpha-numeric levels\")+\n theme_classic()+\n theme(legend.position = \"none\")\n\n\n# boxplots ordered by median CT value\nggplot(data = linelist)+\n geom_boxplot(\n aes(x = fct_reorder(delay_cat, ct_blood, \"median\"),\n y = ct_blood,\n fill = delay_cat))+\n labs(x = \"Delay onset to admission (days)\",\n title = \"Ordered by median CT value in group\")+\n theme_classic()+\n theme(legend.position = \"none\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNote in this example above there are no steps required prior to the ggplot() call - the grouping and calculations are all done internally to the ggplot command.\n\n\nBy “end” value\nUse fct_reorder2() for grouped line plots. It orders the levels (and therefore the legend) to align with the vertical ordering of the lines at the “end” of the plot. Technically speaking, it “orders by the y-values associated with the largest x values.”\nFor example, if you have lines showing case counts by hospital over time, you can apply fct_reorder2() to the color = argument within aes(), such that the vertical order of hospitals appearing in the legend aligns with the order of lines at the terminal end of the plot. Read more in the online documentation.\n\nepidemic_data <- linelist %>% # begin with the linelist \n filter(date_onset < as.Date(\"2014-09-21\")) %>% # cut-off date, for visual clarity\n count( # get case counts per week and by hospital\n epiweek = lubridate::floor_date(date_onset, \"week\"), \n hospital \n ) \n \nggplot(data = epidemic_data)+ # start plot\n geom_line( # make lines\n aes(\n x = epiweek, # x-axis epiweek\n y = n, # height is number of cases per week\n color = fct_reorder2(hospital, epiweek, n)))+ # data grouped and colored by hospital, with factor order by height at end of plot\n labs(title = \"Factor levels (and legend display) by line height at end of plot\",\n color = \"Hospital\") # change legend title", + "text": "11.4 Adjust level order\nThe package forcats offers useful functions to easily adjust the order of a factor’s levels (after a column been defined as class factor):\nThese functions can be applied to a factor column in two contexts:\n\nTo the column in the data frame, as usual, so the transformation is available for any subsequent use of the data.\n\nInside of a plot, so that the change is applied only within the plot.\n\n\nManually\nThis function is used to manually order the factor levels. If used on a non-factor column, the column will first be converted to class factor.\nWithin the parentheses first provide the factor column name, then provide either:\n\nAll the levels in the desired order (as a character vector c()), or,\n\nOne level and it’s corrected placement using the after = argument.\n\nHere is an example of redefining the column delay_cat (which is already class Factor) and specifying all the desired order of levels.\n\n# re-define level order\nlinelist <- linelist %>% \n mutate(delay_cat = fct_relevel(delay_cat, c(\"<2 days\", \"2-5 days\", \">5 days\")))\n\nIf you only want to move one level, you can specify it to fct_relevel() alone and give a number to the after = argument to indicate where in the order it should be. For example, the command below shifts “<2 days” to the second position:\n\n# re-define level order\nlinelist %>% \n mutate(delay_cat = fct_relevel(delay_cat, \"<2 days\", after = 1)) %>% \n tabyl(delay_cat)\n\n\n\nWithin a plot\nThe forcats commands can be used to set the level order in the data frame, or only within a plot. By using the command to “wrap around” the column name within the ggplot() plotting command, you can reverse/relevel/etc. the transformation will only apply within that plot.\nBelow, two plots are created with ggplot() (see the ggplot basics page). In the first, the delay_cat column is mapped to the x-axis of the plot, with it’s default level order as in the data linelist. In the second example it is wrapped within fct_relevel() and the order is changed in the plot.\n\n# Alpha-numeric default order - no adjustment within ggplot\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = delay_cat))\n\n# Factor level order adjusted within ggplot\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = fct_relevel(delay_cat, c(\"<2 days\", \"2-5 days\", \">5 days\"))))\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNote that default x-axis title is now quite complicated - you can overwrite this title with the ggplot2 labs() argument.\n\n\nReverse\nIt is rather common that you want to reverse the level order. Simply wrap the factor with fct_rev().\nNote that if you want to reverse only a plot legend but not the actual factor levels, you can do that with guides() (see ggplot tips).\n\n\nBy frequency\nTo order by frequency that the value appears in the data, use fct_infreq(). Any missing values (NA) will automatically be included at the end, unless they are converted to an explicit level (see this section). You can reverse the order by further wrapping with fct_rev().\nThis function can be used within a ggplot(), as shown below.\n\n# ordered by frequency\nggplot(data = linelist, aes(x = fct_infreq(delay_cat))) +\n geom_bar() +\n labs(x = \"Delay onset to admission (days)\",\n title = \"Ordered by frequency\")\n\n# reversed frequency\nggplot(data = linelist, aes(x = fct_rev(fct_infreq(delay_cat)))) +\n geom_bar() +\n labs(x = \"Delay onset to admission (days)\",\n title = \"Reverse of order by frequency\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nBy appearance\nUse fct_inorder() to set the level order to match the order of appearance in the data, starting from the first row. This can be useful if you first carefully arrange() the data in the data frame, and then use this to set the factor order.\n\n\nBy summary statistic of another column\nYou can use fct_reorder() to order the levels of one column by a summary statistic of another column. Visually, this can result in pleasing plots where the bars/points ascend or descend steadily across the plot.\nIn the examples below, the x-axis is delay_cat, and the y-axis is numeric column ct_blood (cycle-threshold value). Box plots show the CT value distribution by delay_cat group. We want to order the box plots in ascending order by the group median CT value.\nIn the first example below, the default order alpha-numeric level order is used. You can see the box plot heights are jumbled and not in any particular order. In the second example, the delay_cat column (mapped to the x-axis) has been wrapped in fct_reorder(), the column ct_blood is given as the second argument, and “median” is given as the third argument (you could also use “max”, “mean”, “min”, etc). Thus, the order of the levels of delay_cat will now reflect ascending median CT values of each delay_cat group’s median CT value. This is reflected in the second plot - the box plots have been re-arranged to ascend. Note how NA (missing) will appear at the end, unless converted to an explicit level.\n\n# boxplots ordered by original factor levels\nggplot(data = linelist) +\n geom_boxplot(\n aes(x = delay_cat,\n y = ct_blood, \n fill = delay_cat)) +\n labs(x = \"Delay onset to admission (days)\",\n title = \"Ordered by original alpha-numeric levels\") +\n theme_classic() +\n theme(legend.position = \"none\")\n\n\n# boxplots ordered by median CT value\nggplot(data = linelist) +\n geom_boxplot(\n aes(x = fct_reorder(delay_cat, ct_blood, \"median\"),\n y = ct_blood,\n fill = delay_cat)) +\n labs(x = \"Delay onset to admission (days)\",\n title = \"Ordered by median CT value in group\") +\n theme_classic() +\n theme(legend.position = \"none\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNote in this example above there are no steps required prior to the ggplot() call - the grouping and calculations are all done internally to the ggplot command.\n\n\nBy “end” value\nUse fct_reorder2() for grouped line plots. It orders the levels (and therefore the legend) to align with the vertical ordering of the lines at the “end” of the plot. Technically speaking, it “orders by the y-values associated with the largest x values.”\nFor example, if you have lines showing case counts by hospital over time, you can apply fct_reorder2() to the color = argument within aes(), such that the vertical order of hospitals appearing in the legend aligns with the order of lines at the terminal end of the plot. Read more in the online documentation.\n\nepidemic_data <- linelist %>% # begin with the linelist \n filter(date_onset < as.Date(\"2014-09-21\")) %>% # cut-off date, for visual clarity\n count( # get case counts per week and by hospital\n epiweek = lubridate::floor_date(date_onset, \"week\"), \n hospital \n ) \n \nggplot(data = epidemic_data) + # start plot\n geom_line( # make lines\n aes(\n x = epiweek, # x-axis epiweek\n y = n, # height is number of cases per week\n color = fct_reorder2(hospital, epiweek, n))) + # data grouped and colored by hospital, with factor order by height at end of plot\n labs(title = \"Factor levels (and legend display) by line height at end of plot\",\n color = \"Hospital\") # change legend title", "crumbs": [ "Data Management", "11  Factors" @@ -1066,7 +1066,7 @@ "href": "new_pages/factors.html#combine-levels", "title": "11  Factors", "section": "11.6 Combine levels", - "text": "11.6 Combine levels\n\nManually\nYou can adjust the level displays manually manually with fct_recode(). This is like the dplyr function recode() (see Cleaning data and core functions), but it allows the creation of new factor levels. If you use the simple recode() on a factor, new re-coded values will be rejected unless they have already been set as permissible levels.\nThis tool can also be used to “combine” levels, by assigning multiple levels the same re-coded value. Just be careful to not lose information! Consider doing these combining steps in a new column (not over-writing the existing column).\nfct_recode() has a different syntax than recode(). recode() uses OLD = NEW, whereas fct_recode() uses NEW = OLD.\nThe current levels of delay_cat are:\n\nlevels(linelist$delay_cat)\n\n[1] \"<2 days\" \"2-5 days\" \">5 days\" \n\n\nThe new levels are created using syntax fct_recode(column, \"new\" = \"old\", \"new\" = \"old\", \"new\" = \"old\") and printed:\n\nlinelist %>% \n mutate(delay_cat = fct_recode(\n delay_cat,\n \"Less than 2 days\" = \"<2 days\",\n \"2 to 5 days\" = \"2-5 days\",\n \"More than 5 days\" = \">5 days\")) %>% \n tabyl(delay_cat)\n\n delay_cat n percent valid_percent\n Less than 2 days 2990 0.50781250 0.5308949\n 2 to 5 days 2040 0.34646739 0.3622159\n More than 5 days 602 0.10224185 0.1068892\n <NA> 256 0.04347826 NA\n\n\nHere they are manually combined with fct_recode(). Note there is no error raised at the creation of a new level “Less than 5 days”.\n\nlinelist %>% \n mutate(delay_cat = fct_recode(\n delay_cat,\n \"Less than 5 days\" = \"<2 days\",\n \"Less than 5 days\" = \"2-5 days\",\n \"More than 5 days\" = \">5 days\")) %>% \n tabyl(delay_cat)\n\n delay_cat n percent valid_percent\n Less than 5 days 5030 0.85427989 0.8931108\n More than 5 days 602 0.10224185 0.1068892\n <NA> 256 0.04347826 NA\n\n\n\n\nReduce into “Other”\nYou can use fct_other() to manually assign factor levels to an “Other” level. Below, all levels in the column hospital, aside from “Port Hospital” and “Central Hospital”, are combined into “Other”. You can provide a vector to either keep =, or drop =. You can change the display of the “Other” level with other_level =.\n\nlinelist %>% \n mutate(hospital = fct_other( # adjust levels\n hospital,\n keep = c(\"Port Hospital\", \"Central Hospital\"), # keep these separate\n other_level = \"Other Hospital\")) %>% # All others as \"Other Hospital\"\n tabyl(hospital) # print table\n\n hospital n percent\n Central Hospital 454 0.07710598\n Port Hospital 1762 0.29925272\n Other Hospital 3672 0.62364130\n\n\n\n\nReduce by frequency\nYou can combine the least-frequent factor levels automatically using fct_lump().\nTo “lump” together many low-frequency levels into an “Other” group, do one of the following:\n\nSet n = as the number of groups you want to keep. The n most-frequent levels will be kept, and all others will combine into “Other”.\n\nSet prop = as the threshold frequency proportion for levels above which you want to keep. All other values will combine into “Other”.\n\nYou can change the display of the “Other” level with other_level =. Below, all but the two most-frequent hospitals are combined into “Other Hospital”.\n\nlinelist %>% \n mutate(hospital = fct_lump( # adjust levels\n hospital,\n n = 2, # keep top 2 levels\n other_level = \"Other Hospital\")) %>% # all others as \"Other Hospital\"\n tabyl(hospital) # print table\n\n hospital n percent\n Missing 1469 0.2494905\n Port Hospital 1762 0.2992527\n Other Hospital 2657 0.4512568", + "text": "11.6 Combine levels\n\nManually\nYou can adjust the level displays manually manually with fct_recode(). This is like the dplyr function recode() (see Cleaning data and core functions), but it allows the creation of new factor levels. If you use the simple recode() on a factor, new re-coded values will be rejected unless they have already been set as permissible levels.\nThis tool can also be used to “combine” levels, by assigning multiple levels the same re-coded value. Just be careful to not lose information! Consider doing these combining steps in a new column (not over-writing the existing column).\nDANGER: fct_recode() has a different syntax than recode(). recode() uses OLD = NEW, whereas fct_recode() uses NEW = OLD. \nThe current levels of delay_cat are:\n\nlevels(linelist$delay_cat)\n\n[1] \"<2 days\" \"2-5 days\" \">5 days\" \n\n\nThe new levels are created using syntax fct_recode(column, \"new\" = \"old\", \"new\" = \"old\", \"new\" = \"old\") and printed:\n\nlinelist %>% \n mutate(delay_cat = fct_recode(\n delay_cat,\n \"Less than 2 days\" = \"<2 days\",\n \"2 to 5 days\" = \"2-5 days\",\n \"More than 5 days\" = \">5 days\")) %>% \n tabyl(delay_cat)\n\n delay_cat n percent valid_percent\n Less than 2 days 2990 0.50781250 0.5308949\n 2 to 5 days 2040 0.34646739 0.3622159\n More than 5 days 602 0.10224185 0.1068892\n <NA> 256 0.04347826 NA\n\n\nHere they are manually combined with fct_recode(). Note there is no error raised at the creation of a new level “Less than 5 days”.\n\nlinelist %>% \n mutate(delay_cat = fct_recode(\n delay_cat,\n \"Less than 5 days\" = \"<2 days\",\n \"Less than 5 days\" = \"2-5 days\",\n \"More than 5 days\" = \">5 days\")) %>% \n tabyl(delay_cat)\n\n delay_cat n percent valid_percent\n Less than 5 days 5030 0.85427989 0.8931108\n More than 5 days 602 0.10224185 0.1068892\n <NA> 256 0.04347826 NA\n\n\n\n\nReduce into “Other”\nYou can use fct_other() to manually assign factor levels to an “Other” level. Below, all levels in the column hospital, aside from “Port Hospital” and “Central Hospital”, are combined into “Other”. You can provide a vector to either keep =, or drop =. You can change the display of the “Other” level with other_level =.\n\nlinelist %>% \n mutate(hospital = fct_other( # adjust levels\n hospital,\n keep = c(\"Port Hospital\", \"Central Hospital\"), # keep these separate\n other_level = \"Other Hospital\")) %>% # All others as \"Other Hospital\"\n tabyl(hospital) # print table\n\n hospital n percent\n Central Hospital 454 0.07710598\n Port Hospital 1762 0.29925272\n Other Hospital 3672 0.62364130\n\n\n\n\nReduce by frequency\nYou can combine the least-frequent factor levels automatically using fct_lump().\nTo “lump” together many low-frequency levels into an “Other” group, do one of the following:\n\nSet n = as the number of groups you want to keep. The n most-frequent levels will be kept, and all others will combine into “Other”.\n\nSet prop = as the threshold frequency proportion for levels above which you want to keep. All other values will combine into “Other”.\n\nYou can change the display of the “Other” level with other_level =. Below, all but the two most-frequent hospitals are combined into “Other Hospital”.\n\nlinelist %>% \n mutate(hospital = fct_lump( # adjust levels\n hospital,\n n = 2, # keep top 2 levels\n other_level = \"Other Hospital\")) %>% # all others as \"Other Hospital\"\n tabyl(hospital) # print table\n\n hospital n percent\n Missing 1469 0.2494905\n Port Hospital 1762 0.2992527\n Other Hospital 2657 0.4512568", "crumbs": [ "Data Management", "11  Factors" @@ -1077,7 +1077,7 @@ "href": "new_pages/factors.html#show-all-levels", "title": "11  Factors", "section": "11.7 Show all levels", - "text": "11.7 Show all levels\nOne benefit of using factors is to standardise the appearance of plot legends and tables, regardless of which values are actually present in a dataset.\nIf you are preparing many figures (e.g. for multiple jurisdictions) you will want the legends and tables to appear identically even with varying levels of data completion or data composition.\n\nIn plots\nIn a ggplot() figure, simply add the argument drop = FALSE in the relevant scale_xxxx() function. All factor levels will be displayed, regardless of whether they are present in the data. If your factor column levels are displayed using fill =, then in scale_fill_discrete() you include drop = FALSE, as shown below. If your levels are displayed with x = (to the x-axis) color = or size = you would provide this to scale_color_discrete() or scale_size_discrete() accordingly.\nThis example is a stacked bar plot of age category, by hospital. Adding scale_fill_discrete(drop = FALSE) ensures that all age groups appear in the legend, even if not present in the data.\n\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = hospital, fill = age_cat)) +\n scale_fill_discrete(drop = FALSE)+ # show all age groups in the legend, even those not present\n labs(\n title = \"All age groups will appear in legend, even if not present in data\")\n\n\n\n\n\n\n\n\n\n\nIn tables\nBoth the base R table() and tabyl() from janitor will show all factor levels (even unused levels).\nIf you use count() or summarise() from dplyr to make a table, add the argument .drop = FALSE to include counts for all factor levels even those unused.\nRead more in the Descriptive tables page, or at the scale_discrete documentation, or the count() documentation. You can see another example in the Contact tracing page.", + "text": "11.7 Show all levels\nOne benefit of using factors is to standardise the appearance of plot legends and tables, regardless of which values are actually present in a dataset.\nIf you are preparing many figures (e.g. for multiple jurisdictions) you will want the legends and tables to appear identically even with varying levels of data completion or data composition.\n\nIn plots\nIn a ggplot() figure, simply add the argument drop = FALSE in the relevant scale_xxxx() function. All factor levels will be displayed, regardless of whether they are present in the data. If your factor column levels are displayed using fill =, then in scale_fill_discrete() you include drop = FALSE, as shown below. If your levels are displayed with x = (to the x-axis) color = or size = you would provide this to scale_color_discrete() or scale_size_discrete() accordingly.\nThis example is a stacked bar plot of age category, by hospital. Adding scale_fill_discrete(drop = FALSE) ensures that all age groups appear in the legend, even if not present in the data.\n\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = hospital, fill = age_cat)) +\n scale_fill_discrete(drop = FALSE) + # show all age groups in the legend, even those not present\n labs(\n title = \"All age groups will appear in legend, even if not present in data\")\n\n\n\n\n\n\n\n\n\n\nIn tables\nBoth the base R table() and tabyl() from janitor will show all factor levels (even unused levels).\nIf you use count() or summarise() from dplyr to make a table, add the argument .drop = FALSE to include counts for all factor levels even those unused.\nRead more in the Descriptive tables page, or at the scale_discrete documentation, or the count() documentation. You can see another example in the Contact tracing page.", "crumbs": [ "Data Management", "11  Factors" @@ -1088,7 +1088,7 @@ "href": "new_pages/factors.html#epiweeks", "title": "11  Factors", "section": "11.8 Epiweeks", - "text": "11.8 Epiweeks\nPlease see the extensive discussion of how to create epidemiological weeks in the Grouping data page.\nPlease also see the Working with dates page for tips on how to create and format epidemiological weeks.\n\nEpiweeks in a plot\nIf your goal is to create epiweeks to display in a plot, you can do this simply with lubridate’s floor_date(), as explained in the Grouping data page. The values returned will be of class Date with format YYYY-MM-DD. If you use this column in a plot, the dates will naturally order correctly, and you do not need to worry about levels or converting to class Factor. See the ggplot() histogram of onset dates below.\nIn this approach, you can adjust the display of the dates on an axis with scale_x_date(). See the page on Epidemic curves for more information. You can specify a “strptime” display format to the date_labels = argument of scale_x_date(). These formats use “%” placeholders and are covered in the Working with dates page. Use “%Y” to represent a 4-digit year, and either “%W” or “%U” to represent the week number (Monday or Sunday weeks respectively).\n\nlinelist %>% \n mutate(epiweek_date = floor_date(date_onset, \"week\")) %>% # create week column\n ggplot()+ # begin ggplot\n geom_histogram(mapping = aes(x = epiweek_date))+ # histogram of date of onset\n scale_x_date(date_labels = \"%Y-W%W\") # adjust disply of dates to be YYYY-WWw\n\n\n\n\n\n\n\n\n\n\nEpiweeks in the data\nHowever, if your purpose in factoring is not to plot, you can approach this one of two ways:\n\nFor fine control over the display, convert the lubridate epiweek column (YYYY-MM-DD) to the desired display format (YYYY-WWw) within the data frame itself, and then convert it to class Factor.\n\nFirst, use format() from base R to convert the date display from YYYY-MM-DD to YYYY-Www display (see the Working with dates page). In this process the class will be converted to character. Then, convert from character to class Factor with factor().\n\nlinelist <- linelist %>% \n mutate(epiweek_date = floor_date(date_onset, \"week\"), # create epiweeks (YYYY-MM-DD)\n epiweek_formatted = format(epiweek_date, \"%Y-W%W\"), # Convert to display (YYYY-WWw)\n epiweek_formatted = factor(epiweek_formatted)) # Convert to factor\n\n# Display levels\nlevels(linelist$epiweek_formatted)\n\n [1] \"2014-W13\" \"2014-W14\" \"2014-W15\" \"2014-W16\" \"2014-W17\" \"2014-W18\"\n [7] \"2014-W19\" \"2014-W20\" \"2014-W21\" \"2014-W22\" \"2014-W23\" \"2014-W24\"\n[13] \"2014-W25\" \"2014-W26\" \"2014-W27\" \"2014-W28\" \"2014-W29\" \"2014-W30\"\n[19] \"2014-W31\" \"2014-W32\" \"2014-W33\" \"2014-W34\" \"2014-W35\" \"2014-W36\"\n[25] \"2014-W37\" \"2014-W38\" \"2014-W39\" \"2014-W40\" \"2014-W41\" \"2014-W42\"\n[31] \"2014-W43\" \"2014-W44\" \"2014-W45\" \"2014-W46\" \"2014-W47\" \"2014-W48\"\n[37] \"2014-W49\" \"2014-W50\" \"2014-W51\" \"2015-W00\" \"2015-W01\" \"2015-W02\"\n[43] \"2015-W03\" \"2015-W04\" \"2015-W05\" \"2015-W06\" \"2015-W07\" \"2015-W08\"\n[49] \"2015-W09\" \"2015-W10\" \"2015-W11\" \"2015-W12\" \"2015-W13\" \"2015-W14\"\n[55] \"2015-W15\" \"2015-W16\"\n\n\nDANGER: If you place the weeks ahead of the years (“Www-YYYY”) (“%W-%Y”), the default alpha-numeric level ordering will be incorrect (e.g. 01-2015 will be before 35-2014). You could need to manually adjust the order, which would be a long painful process.\n\nFor fast default display, use the aweek package and it’s function date2week(). You can set the week_start = day, and if you set factor = TRUE then the output column is an ordered factor. As a bonus, the factor includes levels for all possible weeks in the span - even if there are no cases that week.\n\n\ndf <- linelist %>% \n mutate(epiweek = date2week(date_onset, week_start = \"Monday\", factor = TRUE))\n\nlevels(df$epiweek)\n\nSee the Working with dates page for more information about aweek. It also offers the reverse function week2date().", + "text": "11.8 Epiweeks\nPlease see the extensive discussion of how to create epidemiological weeks in the Grouping data page. Also see the Working with dates page for tips on how to create and format epidemiological weeks.\n\nEpiweeks in a plot\nIf your goal is to create epiweeks to display in a plot, you can do this simply with lubridate’s floor_date(), as explained in the Grouping data page. The values returned will be of class Date with format YYYY-MM-DD. If you use this column in a plot, the dates will naturally order correctly, and you do not need to worry about levels or converting to class Factor. See the ggplot() histogram of onset dates below.\nIn this approach, you can adjust the display of the dates on an axis with scale_x_date(). See the page on Epidemic curves for more information. You can specify a “strptime” display format to the date_labels = argument of scale_x_date(). These formats use “%” placeholders and are covered in the Working with dates page. Use “%Y” to represent a 4-digit year, and either “%W” or “%U” to represent the week number (Monday or Sunday weeks respectively).\n\nlinelist %>% \n mutate(epiweek_date = floor_date(date_onset, \"week\")) %>% # create week column\n ggplot() + # begin ggplot\n geom_histogram(mapping = aes(x = epiweek_date)) + # histogram of date of onset\n scale_x_date(date_labels = \"%Y-W%W\") # adjust disply of dates to be YYYY-WWw\n\n\n\n\n\n\n\n\n\n\nEpiweeks in the data\nHowever, if your purpose in factoring is not to plot, you can approach this one of two ways:\n\nFor fine control over the display, convert the lubridate epiweek column (YYYY-MM-DD) to the desired display format (YYYY-Www) within the data frame itself, and then convert it to class Factor.\n\nFirst, use format() from base R to convert the date display from YYYY-MM-DD to YYYY-Www display (see the Working with dates page). In this process the class will be converted to character. Then, convert from character to class Factor with factor().\n\nlinelist <- linelist %>% \n mutate(epiweek_date = floor_date(date_onset, \"week\"), # create epiweeks (YYYY-MM-DD)\n epiweek_formatted = format(epiweek_date, \"%Y-W%W\"), # Convert to display (YYYY-WWw)\n epiweek_formatted = factor(epiweek_formatted)) # Convert to factor\n\n# Display levels\nlevels(linelist$epiweek_formatted)\n\n [1] \"2014-W13\" \"2014-W14\" \"2014-W15\" \"2014-W16\" \"2014-W17\" \"2014-W18\"\n [7] \"2014-W19\" \"2014-W20\" \"2014-W21\" \"2014-W22\" \"2014-W23\" \"2014-W24\"\n[13] \"2014-W25\" \"2014-W26\" \"2014-W27\" \"2014-W28\" \"2014-W29\" \"2014-W30\"\n[19] \"2014-W31\" \"2014-W32\" \"2014-W33\" \"2014-W34\" \"2014-W35\" \"2014-W36\"\n[25] \"2014-W37\" \"2014-W38\" \"2014-W39\" \"2014-W40\" \"2014-W41\" \"2014-W42\"\n[31] \"2014-W43\" \"2014-W44\" \"2014-W45\" \"2014-W46\" \"2014-W47\" \"2014-W48\"\n[37] \"2014-W49\" \"2014-W50\" \"2014-W51\" \"2015-W00\" \"2015-W01\" \"2015-W02\"\n[43] \"2015-W03\" \"2015-W04\" \"2015-W05\" \"2015-W06\" \"2015-W07\" \"2015-W08\"\n[49] \"2015-W09\" \"2015-W10\" \"2015-W11\" \"2015-W12\" \"2015-W13\" \"2015-W14\"\n[55] \"2015-W15\" \"2015-W16\"\n\n\nDANGER: If you place the weeks ahead of the years (“Www-YYYY”) (“%W-%Y”), the default alpha-numeric level ordering will be incorrect (e.g. 01-2015 will be before 35-2014). You could need to manually adjust the order, which would be a long painful process.\n\nFor fast default display, use the aweek package and it’s function date2week(). You can set the week_start = day, and if you set factor = TRUE then the output column is an ordered factor. As a bonus, the factor includes levels for all possible weeks in the span - even if there are no cases that week.\n\n\ndf <- linelist %>% \n mutate(epiweek = date2week(date_onset, week_start = \"Monday\", factor = TRUE))\n\nlevels(df$epiweek)\n\nSee the Working with dates page for more information about aweek. It also offers the reverse function week2date().", "crumbs": [ "Data Management", "11  Factors" @@ -1121,7 +1121,7 @@ "href": "new_pages/pivoting.html#preparation", "title": "12  Pivoting data", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n kableExtra, # Build and manipulate complex tables\n tidyverse) # data management + ggplot2 graphics\n\n\n\nImport data\n\n\nMalaria count data\nIn this page, we will use a fictional dataset of daily malaria cases, by facility and age group. If you want to follow along, click here to download (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# Import data\ncount_data <- import(\"malaria_facility_count_data.rds\")\n\nThe first 50 rows are displayed below.\n\n\n\n\n\n\n\n\nLinelist case data\nIn the later part of this page, we will also use the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n# import your dataset\nlinelist <- import(\"linelist_cleaned.xlsx\")", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n kableExtra, # Build and manipulate complex tables\n tidyverse) # data management + ggplot2 graphics\n\n\n\nImport data\n\n\nMalaria count data\nIn this page, we will use a fictional dataset of daily malaria cases, by facility and age group. If you want to follow along, click here to download (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# Import data\ncount_data <- import(\"malaria_facility_count_data.rds\")\n\nThe first 50 rows are displayed below.\n\n\n\n\n\n\n\n\nLinelist case data\nIn the later part of this page, we will also use the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n\nWarning: Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\n\n\n\n# import your dataset\nlinelist <- import(\"linelist_cleaned.rds\")", "crumbs": [ "Data Management", "12  Pivoting data" @@ -1132,7 +1132,7 @@ "href": "new_pages/pivoting.html#wide-to-long", "title": "12  Pivoting data", "section": "12.2 Wide-to-long", - "text": "12.2 Wide-to-long\n\n\n\n\n\n\n\n\n\n\n\n“Wide” format\nData are often entered and stored in a “wide” format - where a subject’s characteristics or responses are stored in a single row. While this may be useful for presentation, it is not ideal for some types of analysis.\nLet us take the count_data dataset imported in the Preparation section above as an example. You can see that each row represents a “facility-day”. The actual case counts (the right-most columns) are stored in a “wide” format such that the information for every age group on a given facility-day is stored in a single row.\n\n\n\n\n\n\nEach observation in this dataset refers to the malaria counts at one of 65 facilities on a given date, ranging from count_data$data_date %>% min() to count_data$data_date %>% max(). These facilities are located in one Province (North) and four Districts (Spring, Bolo, Dingo, and Barnard). The dataset provides the overall counts of malaria, as well as age-specific counts in each of three age groups - <4 years, 5-14 years, and 15 years and older.\n“Wide” data like this are not adhering to “tidy data” standards, because the column headers do not actually represent “variables” - they represent values of a hypothetical “age group” variable.\nThis format can be useful for presenting the information in a table, or for entering data (e.g. in Excel) from case report forms. However, in the analysis stage, these data typically should be transformed to a “longer” format more aligned with “tidy data” standards. The plotting R package ggplot2 in particular works best when data are in a “long” format.\nVisualising the total malaria counts over time poses no difficulty with the data in it’s current format:\n\nggplot(count_data) +\n geom_col(aes(x = data_date, y = malaria_tot), width = 1)\n\n\n\n\n\n\n\n\nHowever, what if we wanted to display the relative contributions of each age group to this total count? In this case, we need to ensure that the variable of interest (age group), appears in the dataset in a single column that can be passed to {ggplot2}’s “mapping aesthetics” aes() argument.\n\n\n\npivot_longer()\nThe tidyr function pivot_longer() makes data “longer”. tidyr is part of the tidyverse of R packages.\nIt accepts a range of columns to transform (specified to cols =). Therefore, it can operate on only a part of a dataset. This is useful for the malaria data, as we only want to pivot the case count columns.\nIn this process, you will end up with two “new” columns - one with the categories (the former column names), and one with the corresponding values (e.g. case counts). You can accept the default names for these new columns, or you can specify your own to names_to = and values_to = respectively.\nLet’s see pivot_longer() in action…\n\n\nStandard pivoting\nWe want to use tidyr’s pivot_longer() function to convert the “wide” data to a “long” format. Specifically, to convert the four numeric columns with data on malaria counts to two new columns: one which holds the age groups and one which holds the corresponding values.\n\ndf_long <- count_data %>% \n pivot_longer(\n cols = c(`malaria_rdt_0-4`, `malaria_rdt_5-14`, `malaria_rdt_15`, `malaria_tot`)\n )\n\ndf_long\n\nNotice that the newly created data frame (df_long) has more rows (12,152 vs 3,038); it has become longer. In fact, it is precisely four times as long, because each row in the original dataset now represents four rows in df_long, one for each of the malaria count observations (<4y, 5-14y, 15y+, and total).\nIn addition to becoming longer, the new dataset has fewer columns (8 vs 10), as the data previously stored in four columns (those beginning with the prefix malaria_) is now stored in two.\nSince the names of these four columns all begin with the prefix malaria_, we could have made use of the handy “tidyselect” function starts_with() to achieve the same result (see the page Cleaning data and core functions for more of these helper functions).\n\n# provide column with a tidyselect helper function\ncount_data %>% \n pivot_longer(\n cols = starts_with(\"malaria_\")\n )\n\n# A tibble: 12,152 × 8\n location_name data_date submitted_date Province District newid name value\n <chr> <date> <date> <chr> <chr> <int> <chr> <int>\n 1 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 11\n 2 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 12\n 3 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 23\n 4 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 46\n 5 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 11\n 6 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 10\n 7 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 5\n 8 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 26\n 9 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malari… 8\n10 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malari… 5\n# ℹ 12,142 more rows\n\n\nor by position:\n\n# provide columns by position\ncount_data %>% \n pivot_longer(\n cols = 6:9\n )\n\nor by named range:\n\n# provide range of consecutive columns\ncount_data %>% \n pivot_longer(\n cols = `malaria_rdt_0-4`:malaria_tot\n )\n\nThese two new columns are given the default names of name and value, but we can override these defaults to provide more meaningful names, which can help remember what is stored within, using the names_to and values_to arguments. Let’s use the names age_group and counts:\n\ndf_long <- \n count_data %>% \n pivot_longer(\n cols = starts_with(\"malaria_\"),\n names_to = \"age_group\",\n values_to = \"counts\"\n )\n\ndf_long\n\n# A tibble: 12,152 × 8\n location_name data_date submitted_date Province District newid age_group \n <chr> <date> <date> <chr> <chr> <int> <chr> \n 1 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_rdt_…\n 2 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_rdt_…\n 3 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_rdt_…\n 4 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_tot \n 5 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_rdt_…\n 6 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_rdt_…\n 7 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_rdt_…\n 8 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_tot \n 9 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malaria_rdt_…\n10 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malaria_rdt_…\n# ℹ 12,142 more rows\n# ℹ 1 more variable: counts <int>\n\n\nWe can now pass this new dataset to {ggplot2}, and map the new column count to the y-axis and new column age_group to the fill = argument (the column internal color). This will display the malaria counts in a stacked bar chart, by age group:\n\nggplot(data = df_long) +\n geom_col(\n mapping = aes(x = data_date, y = counts, fill = age_group),\n width = 1\n )\n\n\n\n\n\n\n\n\nExamine this new plot, and compare it with the plot we created earlier - what has gone wrong?\nWe have encountered a common problem when wrangling surveillance data - we have also included the total counts from the malaria_tot column, so the magnitude of each bar in the plot is twice as high as it should be.\nWe can handle this in a number of ways. We could simply filter these totals from the dataset before we pass it to ggplot():\n\ndf_long %>% \n filter(age_group != \"malaria_tot\") %>% \n ggplot() +\n geom_col(\n aes(x = data_date, y = counts, fill = age_group),\n width = 1\n )\n\n\n\n\n\n\n\n\nAlternatively, we could have excluded this variable when we ran pivot_longer(), thereby maintaining it in the dataset as a separate variable. See how its values “expand” to fill the new rows.\n\ncount_data %>% \n pivot_longer(\n cols = `malaria_rdt_0-4`:malaria_rdt_15, # does not include the totals column\n names_to = \"age_group\",\n values_to = \"counts\"\n )\n\n# A tibble: 9,114 × 9\n location_name data_date submitted_date Province District malaria_tot newid\n <chr> <date> <date> <chr> <chr> <int> <int>\n 1 Facility 1 2020-08-11 2020-08-12 North Spring 46 1\n 2 Facility 1 2020-08-11 2020-08-12 North Spring 46 1\n 3 Facility 1 2020-08-11 2020-08-12 North Spring 46 1\n 4 Facility 2 2020-08-11 2020-08-12 North Bolo 26 2\n 5 Facility 2 2020-08-11 2020-08-12 North Bolo 26 2\n 6 Facility 2 2020-08-11 2020-08-12 North Bolo 26 2\n 7 Facility 3 2020-08-11 2020-08-12 North Dingo 18 3\n 8 Facility 3 2020-08-11 2020-08-12 North Dingo 18 3\n 9 Facility 3 2020-08-11 2020-08-12 North Dingo 18 3\n10 Facility 4 2020-08-11 2020-08-12 North Bolo 49 4\n# ℹ 9,104 more rows\n# ℹ 2 more variables: age_group <chr>, counts <int>\n\n\n\n\nPivoting data of multiple classes\nThe above example works well in situations in which all the columns you want to “pivot longer” are of the same class (character, numeric, logical…).\nHowever, there will be many cases when, as a field epidemiologist, you will be working with data that was prepared by non-specialists and which follow their own non-standard logic - as Hadley Wickham noted (referencing Tolstoy) in his seminal article on Tidy Data principles: “Like families, tidy datasets are all alike but every messy dataset is messy in its own way.”\nOne particularly common problem you will encounter will be the need to pivot columns that contain different classes of data. This pivot will result in storing these different data types in a single column, which is not a good situation. There are various approaches one can take to separate out the mess this creates, but there is an important step you can take using pivot_longer() to avoid creating such a situation yourself.\nTake a situation in which there have been a series of observations at different time steps for each of three items A, B and C. Examples of such items could be individuals (e.g. contacts of an Ebola case being traced each day for 21 days) or remote village health posts being monitored once per year to ensure they are still functional. Let’s use the contact tracing example. Imagine that the data are stored as follows:\n\n\n\n\n\n\nAs can be seen, the data are a bit complicated. Each row stores information about one item, but with the time series running further and further away to the right as time progresses. Moreover, the column classes alternate between date and character values.\nOne particularly bad example of this encountered by this author involved cholera surveillance data, in which 8 new columns of observations were added each day over the course of 4 years. Simply opening the Excel file in which these data were stored took >10 minuntes on my laptop!\nIn order to work with these data, we need to transform the data frame to long format, but keeping the separation between a date column and a character (status) column, for each observation for each item. If we don’t, we might end up with a mixture of variable types in a single column (a very big “no-no” when it comes to data management and tidy data):\n\ndf %>% \n pivot_longer(\n cols = -id,\n names_to = c(\"observation\")\n )\n\n# A tibble: 18 × 3\n id observation value \n <chr> <chr> <chr> \n 1 A obs1_date 2021-04-23\n 2 A obs1_status Healthy \n 3 A obs2_date 2021-04-24\n 4 A obs2_status Healthy \n 5 A obs3_date 2021-04-25\n 6 A obs3_status Unwell \n 7 B obs1_date 2021-04-23\n 8 B obs1_status Healthy \n 9 B obs2_date 2021-04-24\n10 B obs2_status Healthy \n11 B obs3_date 2021-04-25\n12 B obs3_status Healthy \n13 C obs1_date 2021-04-23\n14 C obs1_status Missing \n15 C obs2_date 2021-04-24\n16 C obs2_status Healthy \n17 C obs3_date 2021-04-25\n18 C obs3_status Healthy \n\n\nAbove, our pivot has merged dates and characters into a single value column. R will react by converting the entire column to class character, and the utility of the dates is lost.\nTo prevent this situation, we can take advantage of the syntax structure of the original column names. There is a common naming structure, with the observation number, an underscore, and then either “status” or “date”. We can leverage this syntax to keep these two data types in separate columns after the pivot.\nWe do this by:\n\nProviding a character vector to the names_to = argument, with the second item being (\".value\" ). This special term indicates that the pivoted columns will be split based on a character in their name…\n\nYou must also provide the “splitting” character to the names_sep = argument. In this case, it is the underscore “_“.\n\nThus, the naming and split of new columns is based around the underscore in the existing variable names.\n\ndf_long <- \n df %>% \n pivot_longer(\n cols = -id,\n names_to = c(\"observation\", \".value\"),\n names_sep = \"_\"\n )\n\ndf_long\n\n# A tibble: 9 × 4\n id observation date status \n <chr> <chr> <chr> <chr> \n1 A obs1 2021-04-23 Healthy\n2 A obs2 2021-04-24 Healthy\n3 A obs3 2021-04-25 Unwell \n4 B obs1 2021-04-23 Healthy\n5 B obs2 2021-04-24 Healthy\n6 B obs3 2021-04-25 Healthy\n7 C obs1 2021-04-23 Missing\n8 C obs2 2021-04-24 Healthy\n9 C obs3 2021-04-25 Healthy\n\n\nFinishing touches:\nNote that the date column is currently in character class - we can easily convert this into it’s proper date class using the mutate() and as_date() functions described in the Working with dates page.\nWe may also want to convert the observation column to a numeric format by dropping the “obs” prefix and converting to numeric. We cando this with str_remove_all() from the stringr package (see the Characters and strings page).\n\ndf_long <- \n df_long %>% \n mutate(\n date = date %>% lubridate::as_date(),\n observation = \n observation %>% \n str_remove_all(\"obs\") %>% \n as.numeric()\n )\n\ndf_long\n\n# A tibble: 9 × 4\n id observation date status \n <chr> <dbl> <date> <chr> \n1 A 1 2021-04-23 Healthy\n2 A 2 2021-04-24 Healthy\n3 A 3 2021-04-25 Unwell \n4 B 1 2021-04-23 Healthy\n5 B 2 2021-04-24 Healthy\n6 B 3 2021-04-25 Healthy\n7 C 1 2021-04-23 Missing\n8 C 2 2021-04-24 Healthy\n9 C 3 2021-04-25 Healthy\n\n\nAnd now, we can start to work with the data in this format, e.g. by plotting a descriptive heat tile:\n\nggplot(data = df_long, mapping = aes(x = date, y = id, fill = status)) +\n geom_tile(colour = \"black\") +\n scale_fill_manual(\n values = \n c(\"Healthy\" = \"lightgreen\", \n \"Unwell\" = \"red\", \n \"Missing\" = \"orange\")\n )", + "text": "12.2 Wide-to-long\n\n\n\n\n\n\n\n\n\n\n\n“Wide” format\nData are often entered and stored in a “wide” format - where a subject’s characteristics or responses are stored in a single row. While this may be useful for presentation, it is not ideal for some types of analysis.\nLet us take the count_data dataset imported in the Preparation section above as an example. You can see that each row represents a “facility-day”. The actual case counts (the right-most columns) are stored in a “wide” format such that the information for every age group on a given facility-day is stored in a single row.\n\n\n\n\n\n\nEach observation in this dataset refers to the malaria counts at one of 65 facilities on a given date, ranging from count_data$data_date %>% min() to count_data$data_date %>% max(). These facilities are located in one Province (North) and four District (Spring, Bolo, Dingo, and Barnard). The dataset provides the overall counts of malaria, as well as age-specific counts in each of three age groups - <4 years, 5-14 years, and 15 years and older.\n“Wide” data like this are not adhering to “tidy data” standards, because the column headers do not actually represent “variables” - they represent values of a hypothetical “age group” variable.\nThis format can be useful for presenting the information in a table, or for entering data (e.g. in Excel) from case report forms. However, in the analysis stage, these data typically should be transformed to a “longer” format more aligned with “tidy data” standards. The plotting R package ggplot2 in particular works best when data are in a “long” format.\nVisualising the total malaria counts over time poses no difficulty with the data in it’s current format:\n\nggplot(count_data) +\n geom_col(aes(x = data_date, y = malaria_tot), width = 1)\n\n\n\n\n\n\n\n\nHowever, what if we wanted to display the relative contributions of each age group to this total count? In this case, we need to ensure that the variable of interest (age group), appears in the dataset in a single column that can be passed to {ggplot2}’s “mapping aesthetics” aes() argument.\n\n\n\npivot_longer()\nThe tidyr function pivot_longer() makes data “longer”. tidyr is part of the tidyverse of R packages.\nIt accepts a range of columns to transform (specified to cols =). Therefore, it can operate on only a part of a dataset. This is useful for the malaria data, as we only want to pivot the case count columns.\nIn this process, you will end up with two “new” columns - one with the categories (the former column names), and one with the corresponding values (e.g. case counts). You can accept the default names for these new columns, or you can specify your own to names_to = and values_to = respectively.\nLet’s see pivot_longer() in action.\n\n\nStandard pivoting\nWe want to use tidyr’s pivot_longer() function to convert the “wide” data to a “long” format. Specifically, to convert the four numeric columns with data on malaria counts to two new columns: one which holds the age groups and one which holds the corresponding values.\n\ndf_long <- count_data %>% \n pivot_longer(\n cols = c(`malaria_rdt_0-4`, `malaria_rdt_5-14`, `malaria_rdt_15`, `malaria_tot`)\n )\n\ndf_long\n\nNotice that the newly created data frame (df_long) has more rows (12,152 vs 3,038); it has become longer. In fact, it is precisely four times as long, because each row in the original dataset now represents four rows in df_long, one for each of the malaria count observations (<4y, 5-14y, 15y+, and total).\nIn addition to becoming longer, the new dataset has fewer columns (8 vs 10), as the data previously stored in four columns (those beginning with the prefix malaria_) is now stored in two.\nSince the names of these four columns all begin with the prefix malaria_, we could have made use of the handy “tidyselect” function starts_with() to achieve the same result (see the page Cleaning data and core functions for more of these helper functions).\n\n# provide column with a tidyselect helper function\ncount_data %>% \n pivot_longer(\n cols = starts_with(\"malaria_\")\n )\n\n# A tibble: 12,152 × 8\n location_name data_date submitted_date Province District newid name value\n <chr> <date> <date> <chr> <chr> <int> <chr> <int>\n 1 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 11\n 2 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 12\n 3 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 23\n 4 Facility 1 2020-08-11 2020-08-12 North Spring 1 malari… 46\n 5 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 11\n 6 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 10\n 7 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 5\n 8 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malari… 26\n 9 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malari… 8\n10 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malari… 5\n# ℹ 12,142 more rows\n\n\nor by position:\n\n# provide columns by position\ncount_data %>% \n pivot_longer(\n cols = 6:9\n )\n\nor by named range:\n\n# provide range of consecutive columns\ncount_data %>% \n pivot_longer(\n cols = `malaria_rdt_0-4`:malaria_tot\n )\n\nThese two new columns are given the default names of name and value, but we can override these defaults to provide more meaningful names, which can help remember what is stored within, using the names_to and values_to arguments. Let’s use the names age_group and counts:\n\ndf_long <- \n count_data %>% \n pivot_longer(\n cols = starts_with(\"malaria_\"),\n names_to = \"age_group\",\n values_to = \"counts\"\n )\n\ndf_long\n\n# A tibble: 12,152 × 8\n location_name data_date submitted_date Province District newid age_group \n <chr> <date> <date> <chr> <chr> <int> <chr> \n 1 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_rdt_…\n 2 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_rdt_…\n 3 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_rdt_…\n 4 Facility 1 2020-08-11 2020-08-12 North Spring 1 malaria_tot \n 5 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_rdt_…\n 6 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_rdt_…\n 7 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_rdt_…\n 8 Facility 2 2020-08-11 2020-08-12 North Bolo 2 malaria_tot \n 9 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malaria_rdt_…\n10 Facility 3 2020-08-11 2020-08-12 North Dingo 3 malaria_rdt_…\n# ℹ 12,142 more rows\n# ℹ 1 more variable: counts <int>\n\n\nWe can now pass this new dataset to {ggplot2}, and map the new column count to the y-axis and new column age_group to the fill = argument (the column internal color). This will display the malaria counts in a stacked bar chart, by age group:\n\nggplot(data = df_long) +\n geom_col(\n mapping = aes(x = data_date, y = counts, fill = age_group),\n width = 1\n )\n\n\n\n\n\n\n\n\nExamine this new plot, and compare it with the plot we created earlier - what has gone wrong?\nWe have encountered a common problem when wrangling surveillance data - we have also included the total counts from the malaria_tot column, so the magnitude of each bar in the plot is twice as high as it should be.\nWe can handle this in a number of ways. We could simply filter these totals from the dataset before we pass it to ggplot():\n\ndf_long %>% \n filter(age_group != \"malaria_tot\") %>% \n ggplot() +\n geom_col(\n aes(x = data_date, y = counts, fill = age_group),\n width = 1\n )\n\n\n\n\n\n\n\n\nAlternatively, we could have excluded this variable when we ran pivot_longer(), thereby maintaining it in the dataset as a separate variable. See how its values “expand” to fill the new rows.\n\ncount_data %>% \n pivot_longer(\n cols = `malaria_rdt_0-4`:malaria_rdt_15, # does not include the totals column\n names_to = \"age_group\",\n values_to = \"counts\"\n )\n\n# A tibble: 9,114 × 9\n location_name data_date submitted_date Province District malaria_tot newid\n <chr> <date> <date> <chr> <chr> <int> <int>\n 1 Facility 1 2020-08-11 2020-08-12 North Spring 46 1\n 2 Facility 1 2020-08-11 2020-08-12 North Spring 46 1\n 3 Facility 1 2020-08-11 2020-08-12 North Spring 46 1\n 4 Facility 2 2020-08-11 2020-08-12 North Bolo 26 2\n 5 Facility 2 2020-08-11 2020-08-12 North Bolo 26 2\n 6 Facility 2 2020-08-11 2020-08-12 North Bolo 26 2\n 7 Facility 3 2020-08-11 2020-08-12 North Dingo 18 3\n 8 Facility 3 2020-08-11 2020-08-12 North Dingo 18 3\n 9 Facility 3 2020-08-11 2020-08-12 North Dingo 18 3\n10 Facility 4 2020-08-11 2020-08-12 North Bolo 49 4\n# ℹ 9,104 more rows\n# ℹ 2 more variables: age_group <chr>, counts <int>\n\n\n\n\nPivoting data of multiple classes\nThe above example works well in situations in which all the columns you want to “pivot longer” are of the same class (character, numeric, logical…).\nHowever, there will be many cases when, as a field epidemiologist, you will be working with data that was prepared by non-specialists and which follow their own non-standard logic - as Hadley Wickham noted (referencing Tolstoy) in his seminal article on Tidy Data principles: “Like families, tidy datasets are all alike but every messy dataset is messy in its own way.”\nOne particularly common problem you will encounter will be the need to pivot columns that contain different classes of data. This pivot will result in storing these different data types in a single column, which is not a good situation. There are various approaches one can take to separate out the mess this creates, but there is an important step you can take using pivot_longer() to avoid creating such a situation yourself.\nTake a situation in which there have been a series of observations at different time steps for each of three items A, B and C. Examples of such items could be individuals (e.g. contacts of an Ebola case being traced each day for 21 days) or remote village health posts being monitored once per year to ensure they are still functional.\nLet’s use the contact tracing example. You can download the data here.\n\ndf <- \n tibble::tribble(\n ~id, ~obs1_date, ~obs1_status, ~obs2_date, ~obs2_status, ~obs3_date, ~obs3_status,\n \"A\", \"2021-04-23\", \"Healthy\", \"2021-04-24\", \"Healthy\", \"2021-04-25\", \"Unwell\",\n \"B\", \"2021-04-23\", \"Healthy\", \"2021-04-24\", \"Healthy\", \"2021-04-25\", \"Healthy\",\n \"C\", \"2021-04-23\", \"Missing\", \"2021-04-24\", \"Healthy\", \"2021-04-25\", \"Healthy\"\n ) \n\nDT::datatable(df, rownames = FALSE)\n\n\n\n\n\nAs can be seen, the data are a bit complicated. Each row stores information about one item, but with the time series running further and further away to the right as time progresses. Moreover, the column classes alternate between date and character values.\nOne particularly bad example of this encountered by this author involved cholera surveillance data, in which 8 new columns of observations were added each day over the course of 4 years. Simply opening the Excel file in which these data were stored took >10 minuntes on my laptop!\nIn order to work with these data, we need to transform the data frame to long format, but keeping the separation between a date column and a character (status) column, for each observation for each item. If we don’t, we might end up with a mixture of variable types in a single column (a very big “no-no” when it comes to data management and tidy data):\n\ndf %>% \n pivot_longer(\n cols = -id,\n names_to = c(\"observation\")\n )\n\n# A tibble: 18 × 3\n id observation value \n <chr> <chr> <chr> \n 1 A obs1_date 2021-04-23\n 2 A obs1_status Healthy \n 3 A obs2_date 2021-04-24\n 4 A obs2_status Healthy \n 5 A obs3_date 2021-04-25\n 6 A obs3_status Unwell \n 7 B obs1_date 2021-04-23\n 8 B obs1_status Healthy \n 9 B obs2_date 2021-04-24\n10 B obs2_status Healthy \n11 B obs3_date 2021-04-25\n12 B obs3_status Healthy \n13 C obs1_date 2021-04-23\n14 C obs1_status Missing \n15 C obs2_date 2021-04-24\n16 C obs2_status Healthy \n17 C obs3_date 2021-04-25\n18 C obs3_status Healthy \n\n\nAbove, our pivot has merged dates and characters into a single value column. R will react by converting the entire column to class character, and the utility of the dates is lost.\nTo prevent this situation, we can take advantage of the syntax structure of the original column names. There is a common naming structure, with the observation number, an underscore, and then either “status” or “date”. We can leverage this syntax to keep these two data types in separate columns after the pivot.\nWe do this by:\n\nProviding a character vector to the names_to = argument, with the second item being (\".value\" ). This special term indicates that the pivoted columns will be split based on a character in their name.\n\nYou must also provide the “splitting” character to the names_sep = argument. In this case, it is the underscore “_“.\n\nThus, the naming and split of new columns is based around the underscore in the existing variable names.\n\ndf_long <- \n df %>% \n pivot_longer(\n cols = -id,\n names_to = c(\"observation\", \".value\"),\n names_sep = \"_\"\n )\n\ndf_long\n\n# A tibble: 9 × 4\n id observation date status \n <chr> <chr> <chr> <chr> \n1 A obs1 2021-04-23 Healthy\n2 A obs2 2021-04-24 Healthy\n3 A obs3 2021-04-25 Unwell \n4 B obs1 2021-04-23 Healthy\n5 B obs2 2021-04-24 Healthy\n6 B obs3 2021-04-25 Healthy\n7 C obs1 2021-04-23 Missing\n8 C obs2 2021-04-24 Healthy\n9 C obs3 2021-04-25 Healthy\n\n\nFinishing touches:\nNote that the date column is currently in character class - we can easily convert this into it’s proper date class using the mutate() and as_date() functions described in the Working with dates page.\nWe may also want to convert the observation column to a numeric format by dropping the “obs” prefix and converting to numeric. We can do this with str_remove_all() from the stringr package (see the Characters and strings page).\n\ndf_long <- \n df_long %>% \n mutate(\n date = date %>% lubridate::as_date(),\n observation = \n observation %>% \n str_remove_all(\"obs\") %>% \n as.numeric()\n )\n\ndf_long\n\n# A tibble: 9 × 4\n id observation date status \n <chr> <dbl> <date> <chr> \n1 A 1 2021-04-23 Healthy\n2 A 2 2021-04-24 Healthy\n3 A 3 2021-04-25 Unwell \n4 B 1 2021-04-23 Healthy\n5 B 2 2021-04-24 Healthy\n6 B 3 2021-04-25 Healthy\n7 C 1 2021-04-23 Missing\n8 C 2 2021-04-24 Healthy\n9 C 3 2021-04-25 Healthy\n\n\nAnd now, we can start to work with the data in this format, e.g. by plotting a descriptive heat tile:\n\nggplot(data = df_long, mapping = aes(x = date, y = id, fill = status)) +\n geom_tile(colour = \"black\") +\n scale_fill_manual(\n values = \n c(\"Healthy\" = \"lightgreen\", \n \"Unwell\" = \"red\", \n \"Missing\" = \"orange\")\n )", "crumbs": [ "Data Management", "12  Pivoting data" @@ -1143,7 +1143,7 @@ "href": "new_pages/pivoting.html#long-to-wide", "title": "12  Pivoting data", "section": "12.3 Long-to-wide", - "text": "12.3 Long-to-wide\n\n\n\n\n\n\n\n\n\nIn some instances, we may wish to convert a dataset to a wider format. For this, we can use the pivot_wider() function.\nA typical use-case is when we want to transform the results of an analysis into a format which is more digestible for the reader (such as a [Table for presentation][Tables for presentation]). Usually, this involves transforming a dataset in which information for one subject is are spread over multiple rows into a format in which that information is stored in a single row.\n\nData\nFor this section of the page, we will use the case linelist (see the Preparation section), which contains one row per case.\nHere are the first 50 rows:\n\n\n\n\n\n\nSuppose that we want to know the counts of individuals in the different age groups, by gender:\n\ndf_wide <- \n linelist %>% \n count(age_cat, gender)\n\ndf_wide\n\n age_cat gender n\n1 0-4 f 640\n2 0-4 m 416\n3 0-4 <NA> 39\n4 5-9 f 641\n5 5-9 m 412\n6 5-9 <NA> 42\n7 10-14 f 518\n8 10-14 m 383\n9 10-14 <NA> 40\n10 15-19 f 359\n11 15-19 m 364\n12 15-19 <NA> 20\n13 20-29 f 468\n14 20-29 m 575\n15 20-29 <NA> 30\n16 30-49 f 179\n17 30-49 m 557\n18 30-49 <NA> 18\n19 50-69 f 2\n20 50-69 m 91\n21 50-69 <NA> 2\n22 70+ m 5\n23 70+ <NA> 1\n24 <NA> <NA> 86\n\n\nThis gives us a long dataset that is great for producing visualisations in ggplot2, but not ideal for presentation in a table:\n\nggplot(df_wide) +\n geom_col(aes(x = age_cat, y = n, fill = gender))\n\n\n\n\n\n\n\n\n\n\nPivot wider\nTherefore, we can use pivot_wider() to transform the data into a better format for inclusion as tables in our reports.\nThe argument names_from specifies the column from which to generate the new column names, while the argument values_from specifies the column from which to take the values to populate the cells. The argument id_cols = is optional, but can be provided a vector of column names that should not be pivoted, and will thus identify each row.\n\ntable_wide <- \n df_wide %>% \n pivot_wider(\n id_cols = age_cat,\n names_from = gender,\n values_from = n\n )\n\ntable_wide\n\n# A tibble: 9 × 4\n age_cat f m `NA`\n <fct> <int> <int> <int>\n1 0-4 640 416 39\n2 5-9 641 412 42\n3 10-14 518 383 40\n4 15-19 359 364 20\n5 20-29 468 575 30\n6 30-49 179 557 18\n7 50-69 2 91 2\n8 70+ NA 5 1\n9 <NA> NA NA 86\n\n\nThis table is much more reader-friendly, and therefore better for inclusion in our reports. You can convert into a pretty table with several packages including flextable and knitr. This process is elaborated in the page Tables for presentation.\n\ntable_wide %>% \n janitor::adorn_totals(c(\"row\", \"col\")) %>% # adds row and column totals\n knitr::kable() %>% \n kableExtra::row_spec(row = 10, bold = TRUE) %>% \n kableExtra::column_spec(column = 5, bold = TRUE) \n\n\n\n\n\nage_cat\nf\nm\nNA\nTotal\n\n\n\n\n0-4\n640\n416\n39\n1095\n\n\n5-9\n641\n412\n42\n1095\n\n\n10-14\n518\n383\n40\n941\n\n\n15-19\n359\n364\n20\n743\n\n\n20-29\n468\n575\n30\n1073\n\n\n30-49\n179\n557\n18\n754\n\n\n50-69\n2\n91\n2\n95\n\n\n70+\nNA\n5\n1\n6\n\n\nNA\nNA\nNA\n86\n86\n\n\nTotal\n2807\n2803\n278\n5888", + "text": "12.3 Long-to-wide\n\n\n\n\n\n\n\n\n\nIn some instances, we may wish to convert a dataset to a wider format. For this, we can use the pivot_wider() function.\nA typical use-case is when we want to transform the results of an analysis into a format which is more digestible for the reader (such as a Tables for presentation). Usually, this involves transforming a dataset in which information for one subject is are spread over multiple rows into a format in which that information is stored in a single row.\n\nData\nFor this section of the page, we will use the linelist dataset (see the Preparation section), which contains one row per case.\nHere are the first 50 rows:\n\n\n\n\n\n\nSuppose that we want to know the counts of individuals in the different age groups, by gender:\n\ndf_wide <- \n linelist %>% \n count(age_cat, gender)\n\ndf_wide\n\n age_cat gender n\n1 0-4 f 640\n2 0-4 m 416\n3 0-4 <NA> 39\n4 5-9 f 641\n5 5-9 m 412\n6 5-9 <NA> 42\n7 10-14 f 518\n8 10-14 m 383\n9 10-14 <NA> 40\n10 15-19 f 359\n11 15-19 m 364\n12 15-19 <NA> 20\n13 20-29 f 468\n14 20-29 m 575\n15 20-29 <NA> 30\n16 30-49 f 179\n17 30-49 m 557\n18 30-49 <NA> 18\n19 50-69 f 2\n20 50-69 m 91\n21 50-69 <NA> 2\n22 70+ m 5\n23 70+ <NA> 1\n24 <NA> <NA> 86\n\n\nThis gives us a long dataset that is great for producing visualisations in ggplot2, but not ideal for presentation in a table:\n\nggplot(df_wide) +\n geom_col(aes(x = age_cat, y = n, fill = gender))\n\n\n\n\n\n\n\n\n\n\nPivot wider\nTherefore, we can use pivot_wider() to transform the data into a better format for inclusion as tables in our reports.\nThe argument names_from specifies the column from which to generate the new column names, while the argument values_from specifies the column from which to take the values to populate the cells. The argument id_cols = is optional, but can be provided a vector of column names that should not be pivoted, and will thus identify each row.\n\ntable_wide <- \n df_wide %>% \n pivot_wider(\n id_cols = age_cat,\n names_from = gender,\n values_from = n\n )\n\ntable_wide\n\n# A tibble: 9 × 4\n age_cat f m `NA`\n <fct> <int> <int> <int>\n1 0-4 640 416 39\n2 5-9 641 412 42\n3 10-14 518 383 40\n4 15-19 359 364 20\n5 20-29 468 575 30\n6 30-49 179 557 18\n7 50-69 2 91 2\n8 70+ NA 5 1\n9 <NA> NA NA 86\n\n\nThis table is much more reader-friendly, and therefore better for inclusion in our reports. You can convert into a pretty table with several packages including flextable and knitr. This process is elaborated in the page Tables for presentation.\n\ntable_wide %>% \n janitor::adorn_totals(c(\"row\", \"col\")) %>% # adds row and column totals\n knitr::kable() %>% \n kableExtra::row_spec(row = 10, bold = TRUE) %>% \n kableExtra::column_spec(column = 5, bold = TRUE) \n\n\n\n\nage_cat\nf\nm\nNA\nTotal\n\n\n\n\n0-4\n640\n416\n39\n1095\n\n\n5-9\n641\n412\n42\n1095\n\n\n10-14\n518\n383\n40\n941\n\n\n15-19\n359\n364\n20\n743\n\n\n20-29\n468\n575\n30\n1073\n\n\n30-49\n179\n557\n18\n754\n\n\n50-69\n2\n91\n2\n95\n\n\n70+\nNA\n5\n1\n6\n\n\nNA\nNA\nNA\n86\n86\n\n\nTotal\n2807\n2803\n278\n5888", "crumbs": [ "Data Management", "12  Pivoting data" @@ -1154,7 +1154,7 @@ "href": "new_pages/pivoting.html#fill", "title": "12  Pivoting data", "section": "12.4 Fill", - "text": "12.4 Fill\nIn some situations after a pivot, and more commonly after a bind, we are left with gaps in some cells that we would like to fill.\n\n\nData\nFor example, take two datasets, each with observations for the measurement number, the name of the facility, and the case count at that time. However, the second dataset also has a variable Year.\n\ndf1 <- \n tibble::tribble(\n ~Measurement, ~Facility, ~Cases,\n 1, \"Hosp 1\", 66,\n 2, \"Hosp 1\", 26,\n 3, \"Hosp 1\", 8,\n 1, \"Hosp 2\", 71,\n 2, \"Hosp 2\", 62,\n 3, \"Hosp 2\", 70,\n 1, \"Hosp 3\", 47,\n 2, \"Hosp 3\", 70,\n 3, \"Hosp 3\", 38,\n )\n\ndf1 \n\n# A tibble: 9 × 3\n Measurement Facility Cases\n <dbl> <chr> <dbl>\n1 1 Hosp 1 66\n2 2 Hosp 1 26\n3 3 Hosp 1 8\n4 1 Hosp 2 71\n5 2 Hosp 2 62\n6 3 Hosp 2 70\n7 1 Hosp 3 47\n8 2 Hosp 3 70\n9 3 Hosp 3 38\n\ndf2 <- \n tibble::tribble(\n ~Year, ~Measurement, ~Facility, ~Cases,\n 2000, 1, \"Hosp 4\", 82,\n 2001, 2, \"Hosp 4\", 87,\n 2002, 3, \"Hosp 4\", 46\n )\n\ndf2\n\n# A tibble: 3 × 4\n Year Measurement Facility Cases\n <dbl> <dbl> <chr> <dbl>\n1 2000 1 Hosp 4 82\n2 2001 2 Hosp 4 87\n3 2002 3 Hosp 4 46\n\n\nWhen we perform a bind_rows() to join the two datasets together, the Year variable is filled with NA for those rows where there was no prior information (i.e. the first dataset):\n\ndf_combined <- \n bind_rows(df1, df2) %>% \n arrange(Measurement, Facility)\n\ndf_combined\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 1 66 NA\n 2 1 Hosp 2 71 NA\n 3 1 Hosp 3 47 NA\n 4 1 Hosp 4 82 2000\n 5 2 Hosp 1 26 NA\n 6 2 Hosp 2 62 NA\n 7 2 Hosp 3 70 NA\n 8 2 Hosp 4 87 2001\n 9 3 Hosp 1 8 NA\n10 3 Hosp 2 70 NA\n11 3 Hosp 3 38 NA\n12 3 Hosp 4 46 2002\n\n\n\n\n\nfill()\nIn this case, Year is a useful variable to include, particularly if we want to explore trends over time. Therefore, we use fill() to fill in those empty cells, by specifying the column to fill and the direction (in this case up):\n\ndf_combined %>% \n fill(Year, .direction = \"up\")\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 1 66 2000\n 2 1 Hosp 2 71 2000\n 3 1 Hosp 3 47 2000\n 4 1 Hosp 4 82 2000\n 5 2 Hosp 1 26 2001\n 6 2 Hosp 2 62 2001\n 7 2 Hosp 3 70 2001\n 8 2 Hosp 4 87 2001\n 9 3 Hosp 1 8 2002\n10 3 Hosp 2 70 2002\n11 3 Hosp 3 38 2002\n12 3 Hosp 4 46 2002\n\n\nAlternatively, we can rearrange the data so that we would need to fill in a downward direction:\n\ndf_combined <- \n df_combined %>% \n arrange(Measurement, desc(Facility))\n\ndf_combined\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 4 82 2000\n 2 1 Hosp 3 47 NA\n 3 1 Hosp 2 71 NA\n 4 1 Hosp 1 66 NA\n 5 2 Hosp 4 87 2001\n 6 2 Hosp 3 70 NA\n 7 2 Hosp 2 62 NA\n 8 2 Hosp 1 26 NA\n 9 3 Hosp 4 46 2002\n10 3 Hosp 3 38 NA\n11 3 Hosp 2 70 NA\n12 3 Hosp 1 8 NA\n\ndf_combined <- \n df_combined %>% \n fill(Year, .direction = \"down\")\n\ndf_combined\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 4 82 2000\n 2 1 Hosp 3 47 2000\n 3 1 Hosp 2 71 2000\n 4 1 Hosp 1 66 2000\n 5 2 Hosp 4 87 2001\n 6 2 Hosp 3 70 2001\n 7 2 Hosp 2 62 2001\n 8 2 Hosp 1 26 2001\n 9 3 Hosp 4 46 2002\n10 3 Hosp 3 38 2002\n11 3 Hosp 2 70 2002\n12 3 Hosp 1 8 2002\n\n\nWe now have a useful dataset for plotting:\n\nggplot(df_combined) +\n aes(Year, Cases, fill = Facility) +\n geom_col()\n\n\n\n\n\n\n\n\nBut less useful for presenting in a table, so let’s practice converting this long, untidy dataframe into a wider, tidy dataframe:\n\ndf_combined %>% \n pivot_wider(\n id_cols = c(Measurement, Facility),\n names_from = \"Year\",\n values_from = \"Cases\"\n ) %>% \n arrange(Facility) %>% \n janitor::adorn_totals(c(\"row\", \"col\")) %>% \n knitr::kable() %>% \n kableExtra::row_spec(row = 5, bold = TRUE) %>% \n kableExtra::column_spec(column = 5, bold = TRUE) \n\n\n\n\n\nMeasurement\nFacility\n2000\n2001\n2002\nTotal\n\n\n\n\n1\nHosp 1\n66\nNA\nNA\n66\n\n\n2\nHosp 1\nNA\n26\nNA\n26\n\n\n3\nHosp 1\nNA\nNA\n8\n8\n\n\n1\nHosp 2\n71\nNA\nNA\n71\n\n\n2\nHosp 2\nNA\n62\nNA\n62\n\n\n3\nHosp 2\nNA\nNA\n70\n70\n\n\n1\nHosp 3\n47\nNA\nNA\n47\n\n\n2\nHosp 3\nNA\n70\nNA\n70\n\n\n3\nHosp 3\nNA\nNA\n38\n38\n\n\n1\nHosp 4\n82\nNA\nNA\n82\n\n\n2\nHosp 4\nNA\n87\nNA\n87\n\n\n3\nHosp 4\nNA\nNA\n46\n46\n\n\nTotal\n-\n266\n245\n162\n673\n\n\n\n\n\n\n\n\nN.B. In this case, we had to specify to only include the three variables Facility, Year, and Cases as the additional variable Measurement would interfere with the creation of the table:\n\ndf_combined %>% \n pivot_wider(\n names_from = \"Year\",\n values_from = \"Cases\"\n ) %>% \n knitr::kable()\n\n\n\n\nMeasurement\nFacility\n2000\n2001\n2002\n\n\n\n\n1\nHosp 4\n82\nNA\nNA\n\n\n1\nHosp 3\n47\nNA\nNA\n\n\n1\nHosp 2\n71\nNA\nNA\n\n\n1\nHosp 1\n66\nNA\nNA\n\n\n2\nHosp 4\nNA\n87\nNA\n\n\n2\nHosp 3\nNA\n70\nNA\n\n\n2\nHosp 2\nNA\n62\nNA\n\n\n2\nHosp 1\nNA\n26\nNA\n\n\n3\nHosp 4\nNA\nNA\n46\n\n\n3\nHosp 3\nNA\nNA\n38\n\n\n3\nHosp 2\nNA\nNA\n70\n\n\n3\nHosp 1\nNA\nNA\n8", + "text": "12.4 Fill\nIn some situations after a pivot, and more commonly after a bind, we are left with gaps in some cells that we would like to fill.\n\n\nData\nFor example, take two datasets, each with observations for the measurement number, the name of the facility, and the case count at that time. However, the second dataset also has a variable Year.\n\ndf1 <- \n tibble::tribble(\n ~Measurement, ~Facility, ~Cases,\n 1, \"Hosp 1\", 66,\n 2, \"Hosp 1\", 26,\n 3, \"Hosp 1\", 8,\n 1, \"Hosp 2\", 71,\n 2, \"Hosp 2\", 62,\n 3, \"Hosp 2\", 70,\n 1, \"Hosp 3\", 47,\n 2, \"Hosp 3\", 70,\n 3, \"Hosp 3\", 38,\n )\n\ndf1 \n\n# A tibble: 9 × 3\n Measurement Facility Cases\n <dbl> <chr> <dbl>\n1 1 Hosp 1 66\n2 2 Hosp 1 26\n3 3 Hosp 1 8\n4 1 Hosp 2 71\n5 2 Hosp 2 62\n6 3 Hosp 2 70\n7 1 Hosp 3 47\n8 2 Hosp 3 70\n9 3 Hosp 3 38\n\ndf2 <- \n tibble::tribble(\n ~Year, ~Measurement, ~Facility, ~Cases,\n 2000, 1, \"Hosp 4\", 82,\n 2001, 2, \"Hosp 4\", 87,\n 2002, 3, \"Hosp 4\", 46\n )\n\ndf2\n\n# A tibble: 3 × 4\n Year Measurement Facility Cases\n <dbl> <dbl> <chr> <dbl>\n1 2000 1 Hosp 4 82\n2 2001 2 Hosp 4 87\n3 2002 3 Hosp 4 46\n\n\nWhen we perform a bind_rows() to join the two datasets together, the Year variable is filled with NA for those rows where there was no prior information (i.e. the first dataset):\n\ndf_combined <- \n bind_rows(df1, df2) %>% \n arrange(Measurement, Facility)\n\ndf_combined\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 1 66 NA\n 2 1 Hosp 2 71 NA\n 3 1 Hosp 3 47 NA\n 4 1 Hosp 4 82 2000\n 5 2 Hosp 1 26 NA\n 6 2 Hosp 2 62 NA\n 7 2 Hosp 3 70 NA\n 8 2 Hosp 4 87 2001\n 9 3 Hosp 1 8 NA\n10 3 Hosp 2 70 NA\n11 3 Hosp 3 38 NA\n12 3 Hosp 4 46 2002\n\n\n\n\n\nfill()\nIn this case, Year is a useful variable to include, particularly if we want to explore trends over time. Therefore, we use fill() to fill in those empty cells, by specifying the column to fill and the direction (in this case up):\n\ndf_combined %>% \n fill(Year, .direction = \"up\")\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 1 66 2000\n 2 1 Hosp 2 71 2000\n 3 1 Hosp 3 47 2000\n 4 1 Hosp 4 82 2000\n 5 2 Hosp 1 26 2001\n 6 2 Hosp 2 62 2001\n 7 2 Hosp 3 70 2001\n 8 2 Hosp 4 87 2001\n 9 3 Hosp 1 8 2002\n10 3 Hosp 2 70 2002\n11 3 Hosp 3 38 2002\n12 3 Hosp 4 46 2002\n\n\nAlternatively, we can rearrange the data so that we would need to fill in a downward direction:\n\ndf_combined <- \n df_combined %>% \n arrange(Measurement, desc(Facility))\n\ndf_combined\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 4 82 2000\n 2 1 Hosp 3 47 NA\n 3 1 Hosp 2 71 NA\n 4 1 Hosp 1 66 NA\n 5 2 Hosp 4 87 2001\n 6 2 Hosp 3 70 NA\n 7 2 Hosp 2 62 NA\n 8 2 Hosp 1 26 NA\n 9 3 Hosp 4 46 2002\n10 3 Hosp 3 38 NA\n11 3 Hosp 2 70 NA\n12 3 Hosp 1 8 NA\n\ndf_combined <- \n df_combined %>% \n fill(Year, .direction = \"down\")\n\ndf_combined\n\n# A tibble: 12 × 4\n Measurement Facility Cases Year\n <dbl> <chr> <dbl> <dbl>\n 1 1 Hosp 4 82 2000\n 2 1 Hosp 3 47 2000\n 3 1 Hosp 2 71 2000\n 4 1 Hosp 1 66 2000\n 5 2 Hosp 4 87 2001\n 6 2 Hosp 3 70 2001\n 7 2 Hosp 2 62 2001\n 8 2 Hosp 1 26 2001\n 9 3 Hosp 4 46 2002\n10 3 Hosp 3 38 2002\n11 3 Hosp 2 70 2002\n12 3 Hosp 1 8 2002\n\n\nWe now have a useful dataset for plotting:\n\nggplot(df_combined) +\n aes(Year, Cases, fill = Facility) +\n geom_col()\n\n\n\n\n\n\n\n\nBut less useful for presenting in a table, so let’s practice converting this long, untidy dataframe into a wider, tidy dataframe:\n\ndf_combined %>% \n pivot_wider(\n id_cols = c(Measurement, Facility),\n names_from = \"Year\",\n values_from = \"Cases\"\n ) %>% \n arrange(Facility) %>% \n janitor::adorn_totals(c(\"row\", \"col\")) %>% \n knitr::kable() %>% \n kableExtra::row_spec(row = 5, bold = TRUE) %>% \n kableExtra::column_spec(column = 5, bold = TRUE) \n\n\n\n\nMeasurement\nFacility\n2000\n2001\n2002\nTotal\n\n\n\n\n1\nHosp 1\n66\nNA\nNA\n66\n\n\n2\nHosp 1\nNA\n26\nNA\n26\n\n\n3\nHosp 1\nNA\nNA\n8\n8\n\n\n1\nHosp 2\n71\nNA\nNA\n71\n\n\n2\nHosp 2\nNA\n62\nNA\n62\n\n\n3\nHosp 2\nNA\nNA\n70\n70\n\n\n1\nHosp 3\n47\nNA\nNA\n47\n\n\n2\nHosp 3\nNA\n70\nNA\n70\n\n\n3\nHosp 3\nNA\nNA\n38\n38\n\n\n1\nHosp 4\n82\nNA\nNA\n82\n\n\n2\nHosp 4\nNA\n87\nNA\n87\n\n\n3\nHosp 4\nNA\nNA\n46\n46\n\n\nTotal\n-\n266\n245\n162\n673\n\n\n\n\n\n\n\nN.B. In this case, we had to specify to only include the three variables Facility, Year, and Cases as the additional variable Measurement would interfere with the creation of the table:\n\ndf_combined %>% \n pivot_wider(\n names_from = \"Year\",\n values_from = \"Cases\"\n ) %>% \n knitr::kable()\n\n\n\n\nMeasurement\nFacility\n2000\n2001\n2002\n\n\n\n\n1\nHosp 4\n82\nNA\nNA\n\n\n1\nHosp 3\n47\nNA\nNA\n\n\n1\nHosp 2\n71\nNA\nNA\n\n\n1\nHosp 1\n66\nNA\nNA\n\n\n2\nHosp 4\nNA\n87\nNA\n\n\n2\nHosp 3\nNA\n70\nNA\n\n\n2\nHosp 2\nNA\n62\nNA\n\n\n2\nHosp 1\nNA\n26\nNA\n\n\n3\nHosp 4\nNA\nNA\n46\n\n\n3\nHosp 3\nNA\nNA\n38\n\n\n3\nHosp 2\nNA\nNA\n70\n\n\n3\nHosp 1\nNA\nNA\n8", "crumbs": [ "Data Management", "12  Pivoting data" @@ -1187,7 +1187,7 @@ "href": "new_pages/grouping.html#preparation", "title": "13  Grouping data", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # to import data\n here, # to locate files\n tidyverse, # to clean, handle, and plot the data (includes dplyr)\n janitor) # adding total rows and columns\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of linelist:", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # to import data\n here, # to locate files\n tidyverse, # to clean, handle, and plot the data (includes dplyr)\n janitor # adding total rows and columns\n ) \n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of linelist:", "crumbs": [ "Data Management", "13  Grouping data" @@ -1198,7 +1198,7 @@ "href": "new_pages/grouping.html#grouping", "title": "13  Grouping data", "section": "13.2 Grouping", - "text": "13.2 Grouping\nThe function group_by() from dplyr groups the rows by the unique values in the column specified to it. If multiple columns are specified, rows are grouped by the unique combinations of values across the columns. Each unique value (or combination of values) constitutes a group. Subsequent changes to the dataset or calculations can then be performed within the context of each group.\nFor example, the command below takes the linelist and groups the rows by unique values in the column outcome, saving the output as a new data frame ll_by_outcome. The grouping column(s) are placed inside the parentheses of the function group_by().\n\nll_by_outcome <- linelist %>% \n group_by(outcome)\n\nNote that there is no perceptible change to the dataset after running group_by(), until another dplyr verb such as mutate(), summarise(), or arrange() is applied on the “grouped” data frame.\nYou can however “see” the groupings by printing the data frame. When you print a grouped data frame, you will see it has been transformed into a tibble class object which, when printed, displays which groupings have been applied and how many groups there are - written just above the header row.\n\n# print to see which groups are active\nll_by_outcome\n\n# A tibble: 5,888 × 30\n# Groups: outcome [3]\n case_id generation date_infection date_onset date_hospitalisation\n <chr> <dbl> <date> <date> <date> \n 1 5fe599 4 2014-05-08 2014-05-13 2014-05-15 \n 2 8689b7 4 NA 2014-05-13 2014-05-14 \n 3 11f8ea 2 NA 2014-05-16 2014-05-18 \n 4 b8812a 3 2014-05-04 2014-05-18 2014-05-20 \n 5 893f25 3 2014-05-18 2014-05-21 2014-05-22 \n 6 be99c8 3 2014-05-03 2014-05-22 2014-05-23 \n 7 07e3e8 4 2014-05-22 2014-05-27 2014-05-29 \n 8 369449 4 2014-05-28 2014-06-02 2014-06-03 \n 9 f393b4 4 NA 2014-06-05 2014-06-06 \n10 1389ca 4 NA 2014-06-05 2014-06-07 \n# ℹ 5,878 more rows\n# ℹ 25 more variables: date_outcome <date>, outcome <chr>, gender <chr>,\n# age <dbl>, age_unit <chr>, age_years <dbl>, age_cat <fct>, age_cat5 <fct>,\n# hospital <chr>, lon <dbl>, lat <dbl>, infector <chr>, source <chr>,\n# wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>, chills <chr>,\n# cough <chr>, aches <chr>, vomit <chr>, temp <dbl>, time_admission <chr>,\n# bmi <dbl>, days_onset_hosp <dbl>\n\n\n\nUnique groups\nThe groups created reflect each unique combination of values across the grouping columns.\nTo see the groups and the number of rows in each group, pass the grouped data to tally(). To see just the unique groups without counts you can pass to group_keys().\nSee below that there are three unique values in the grouping column outcome: “Death”, “Recover”, and NA. See that there were nrow(linelist %>% filter(outcome == \"Death\")) deaths, nrow(linelist %>% filter(outcome == \"Recover\")) recoveries, and nrow(linelist %>% filter(is.na(outcome))) with no outcome recorded.\n\nlinelist %>% \n group_by(outcome) %>% \n tally()\n\n# A tibble: 3 × 2\n outcome n\n <chr> <int>\n1 Death 2582\n2 Recover 1983\n3 <NA> 1323\n\n\nYou can group by more than one column. Below, the data frame is grouped by outcome and gender, and then tallied. Note how each unique combination of outcome and gender is registered as its own group - including missing values for either column.\n\nlinelist %>% \n group_by(outcome, gender) %>% \n tally()\n\n# A tibble: 9 × 3\n# Groups: outcome [3]\n outcome gender n\n <chr> <chr> <int>\n1 Death f 1227\n2 Death m 1228\n3 Death <NA> 127\n4 Recover f 953\n5 Recover m 950\n6 Recover <NA> 80\n7 <NA> f 627\n8 <NA> m 625\n9 <NA> <NA> 71\n\n\n\n\nNew columns\nYou can also create a new grouping column within the group_by() statement. This is equivalent to calling mutate() before the group_by(). For a quick tabulation this style can be handy, but for more clarity in your code consider creating this column in its own mutate() step and then piping to group_by().\n\n# group dat based on a binary column created *within* the group_by() command\nlinelist %>% \n group_by(\n age_class = ifelse(age >= 18, \"adult\", \"child\")) %>% \n tally(sort = T)\n\n# A tibble: 3 × 2\n age_class n\n <chr> <int>\n1 child 3618\n2 adult 2184\n3 <NA> 86\n\n\n\n\nAdd/drop grouping columns\nBy default, if you run group_by() on data that are already grouped, the old groups will be removed and the new one(s) will apply. If you want to add new groups to the existing ones, include the argument .add = TRUE.\n\n# Grouped by outcome\nby_outcome <- linelist %>% \n group_by(outcome)\n\n# Add grouping by gender in addition\nby_outcome_gender <- by_outcome %>% \n group_by(gender, .add = TRUE)\n\n** Keep all groups**\nIf you group on a column of class factor there may be levels of the factor that are not currently present in the data. If you group on this column, by default those non-present levels are dropped and not included as groups. To change this so that all levels appear as groups (even if not present in the data), set .drop = FALSE in your group_by() command.", + "text": "13.2 Grouping\nThe function group_by() from dplyr groups the rows by the unique values in the column specified to it. If multiple columns are specified, rows are grouped by the unique combinations of values across the columns. Each unique value (or combination of values) constitutes a group. Subsequent changes to the dataset or calculations can then be performed within the context of each group.\nFor example, the command below takes the linelist and groups the rows by unique values in the column outcome, saving the output as a new data frame ll_by_outcome. The grouping column(s) are placed inside the parentheses of the function group_by().\n\nll_by_outcome <- linelist %>% \n group_by(outcome)\n\nNote that there is no perceptible change to the dataset after running group_by(), until another dplyr verb such as mutate(), summarise(), or arrange() is applied on the “grouped” data frame.\nYou can however “see” the groupings by printing the data frame. When you print a grouped data frame, you will see it has been transformed into a tibble class object which, when printed, displays which groupings have been applied and how many groups there are - written just above the header row.\n\n# print to see which groups are active\nll_by_outcome\n\n# A tibble: 5,888 × 30\n# Groups: outcome [3]\n case_id generation date_infection date_onset date_hospitalisation\n <chr> <dbl> <date> <date> <date> \n 1 5fe599 4 2014-05-08 2014-05-13 2014-05-15 \n 2 8689b7 4 NA 2014-05-13 2014-05-14 \n 3 11f8ea 2 NA 2014-05-16 2014-05-18 \n 4 b8812a 3 2014-05-04 2014-05-18 2014-05-20 \n 5 893f25 3 2014-05-18 2014-05-21 2014-05-22 \n 6 be99c8 3 2014-05-03 2014-05-22 2014-05-23 \n 7 07e3e8 4 2014-05-22 2014-05-27 2014-05-29 \n 8 369449 4 2014-05-28 2014-06-02 2014-06-03 \n 9 f393b4 4 NA 2014-06-05 2014-06-06 \n10 1389ca 4 NA 2014-06-05 2014-06-07 \n# ℹ 5,878 more rows\n# ℹ 25 more variables: date_outcome <date>, outcome <chr>, gender <chr>,\n# age <dbl>, age_unit <chr>, age_years <dbl>, age_cat <fct>, age_cat5 <fct>,\n# hospital <chr>, lon <dbl>, lat <dbl>, infector <chr>, source <chr>,\n# wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>, chills <chr>,\n# cough <chr>, aches <chr>, vomit <chr>, temp <dbl>, time_admission <chr>,\n# bmi <dbl>, days_onset_hosp <dbl>\n\n\n\nUnique groups\nThe groups created reflect each unique combination of values across the grouping columns.\nTo see the groups and the number of rows in each group, pass the grouped data to tally(). To see just the unique groups without counts you can pass to group_keys().\nSee below that there are three unique values in the grouping column outcome: “Death”, “Recover”, and NA. See that there were nrow(linelist %>% filter(outcome == \"Death\")) deaths, nrow(linelist %>% filter(outcome == \"Recover\")) recoveries, and nrow(linelist %>% filter(is.na(outcome))) with no outcome recorded.\n\nlinelist %>% \n group_by(outcome) %>% \n tally()\n\n# A tibble: 3 × 2\n outcome n\n <chr> <int>\n1 Death 2582\n2 Recover 1983\n3 <NA> 1323\n\n\nYou can group by more than one column. Below, the data frame is grouped by outcome and gender, and then tallied. Note how each unique combination of outcome and gender is registered as its own group - including missing values for either column.\n\nlinelist %>% \n group_by(outcome, gender) %>% \n tally()\n\n# A tibble: 9 × 3\n# Groups: outcome [3]\n outcome gender n\n <chr> <chr> <int>\n1 Death f 1227\n2 Death m 1228\n3 Death <NA> 127\n4 Recover f 953\n5 Recover m 950\n6 Recover <NA> 80\n7 <NA> f 627\n8 <NA> m 625\n9 <NA> <NA> 71\n\n\n\n\nNew columns\nYou can also create a new grouping column within the group_by() statement. This is equivalent to calling mutate() before the group_by(). For a quick tabulation this style can be handy, but for more clarity in your code consider creating this column in its own mutate() step and then piping to group_by().\n\n# group dat based on a binary column created *within* the group_by() command\nlinelist %>% \n group_by(\n age_class = ifelse(age >= 18, \"adult\", \"child\")) %>% \n tally(sort = T)\n\n# A tibble: 3 × 2\n age_class n\n <chr> <int>\n1 child 3618\n2 adult 2184\n3 <NA> 86\n\n\n\n\nAdd/drop grouping columns\nBy default, if you run group_by() on data that are already grouped, the old groups will be removed and the new one(s) will apply. If you want to add new groups to the existing ones, include the argument .add = TRUE.\n\n# Grouped by outcome\nby_outcome <- linelist %>% \n group_by(outcome)\n\n# Add grouping by gender in addition\nby_outcome_gender <- by_outcome %>% \n group_by(gender, .add = TRUE)\n\nKeep all groups\nIf you group on a column of class factor there may be levels of the factor that are not currently present in the data. If you group on this column, by default those non-present levels are dropped and not included as groups. To change this so that all levels appear as groups (even if not present in the data), set .drop = FALSE in your group_by() command.", "crumbs": [ "Data Management", "13  Grouping data" @@ -1231,7 +1231,7 @@ "href": "new_pages/grouping.html#counts-and-tallies", "title": "13  Grouping data", "section": "13.5 Counts and tallies", - "text": "13.5 Counts and tallies\ncount() and tally() provide similar functionality but are different. Read more about the distinction between tally() and count() here\n\ntally()\ntally() is shorthand for summarise(n = n()), and does not group data. Thus, to achieve grouped tallys it must follow a group_by() command. You can add sort = TRUE to see the largest groups first.\n\nlinelist %>% \n tally()\n\n n\n1 5888\n\n\n\nlinelist %>% \n group_by(outcome) %>% \n tally(sort = TRUE)\n\n# A tibble: 3 × 2\n outcome n\n <chr> <int>\n1 Death 2582\n2 Recover 1983\n3 <NA> 1323\n\n\n\n\ncount()\nIn contrast, count() does the following:\n\napplies group_by() on the specified column(s)\n\napplies summarise() and returns column n with the number of rows per group\n\napplies ungroup()\n\n\nlinelist %>% \n count(outcome)\n\n outcome n\n1 Death 2582\n2 Recover 1983\n3 <NA> 1323\n\n\nJust like with group_by() you can create a new column within the count() command:\n\nlinelist %>% \n count(age_class = ifelse(age >= 18, \"adult\", \"child\"), sort = T)\n\n age_class n\n1 child 3618\n2 adult 2184\n3 <NA> 86\n\n\ncount() can be called multiple times, with the functionality “rolling up”. For example, to summarise the number of hospitals present for each gender, run the following. Note, the name of the final column is changed from default “n” for clarity (with name =).\n\nlinelist %>% \n # produce counts by unique outcome-gender groups\n count(gender, hospital) %>% \n # gather rows by gender (3) and count number of hospitals per gender (6)\n count(gender, name = \"hospitals per gender\" ) \n\n gender hospitals per gender\n1 f 6\n2 m 6\n3 <NA> 6\n\n\n\n\nAdd counts\nIn contrast to count() and summarise(), you can use add_count() to add a new column n with the counts of rows per group while retaining all the other data frame columns.\nThis means that a group’s count number, in the new column n, will be printed in each row of the group. For demonstration purposes, we add this column and then re-arrange the columns for easier viewing. See the section below on filter on group size for another example.\n\nlinelist %>% \n as_tibble() %>% # convert to tibble for nicer printing \n add_count(hospital) %>% # add column n with counts by hospital\n select(hospital, n, everything()) # re-arrange for demo purposes\n\n# A tibble: 5,888 × 31\n hospital n case_id generation date_infection date_onset\n <chr> <int> <chr> <dbl> <date> <date> \n 1 Other 885 5fe599 4 2014-05-08 2014-05-13\n 2 Missing 1469 8689b7 4 NA 2014-05-13\n 3 St. Mark's Maternity Hosp… 422 11f8ea 2 NA 2014-05-16\n 4 Port Hospital 1762 b8812a 3 2014-05-04 2014-05-18\n 5 Military Hospital 896 893f25 3 2014-05-18 2014-05-21\n 6 Port Hospital 1762 be99c8 3 2014-05-03 2014-05-22\n 7 Missing 1469 07e3e8 4 2014-05-22 2014-05-27\n 8 Missing 1469 369449 4 2014-05-28 2014-06-02\n 9 Missing 1469 f393b4 4 NA 2014-06-05\n10 Missing 1469 1389ca 4 NA 2014-06-05\n# ℹ 5,878 more rows\n# ℹ 25 more variables: date_hospitalisation <date>, date_outcome <date>,\n# outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,\n# age_cat <fct>, age_cat5 <fct>, lon <dbl>, lat <dbl>, infector <chr>,\n# source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>,\n# chills <chr>, cough <chr>, aches <chr>, vomit <chr>, temp <dbl>,\n# time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>\n\n\n\n\nAdd totals\nTo easily add total sum rows or columns after using tally() or count(), see the janitor section of the Descriptive tables page. This package offers functions like adorn_totals() and adorn_percentages() to add totals and convert to show percentages. Below is a brief example:\n\nlinelist %>% # case linelist\n tabyl(age_cat, gender) %>% # cross-tabulate counts of two columns\n adorn_totals(where = \"row\") %>% # add a total row\n adorn_percentages(denominator = \"col\") %>% # convert to proportions with column denominator\n adorn_pct_formatting() %>% # convert proportions to percents\n adorn_ns(position = \"front\") %>% # display as: \"count (percent)\"\n adorn_title( # adjust titles\n row_name = \"Age Category\",\n col_name = \"Gender\")\n\n Gender \n Age Category f m NA_\n 0-4 640 (22.8%) 416 (14.8%) 39 (14.0%)\n 5-9 641 (22.8%) 412 (14.7%) 42 (15.1%)\n 10-14 518 (18.5%) 383 (13.7%) 40 (14.4%)\n 15-19 359 (12.8%) 364 (13.0%) 20 (7.2%)\n 20-29 468 (16.7%) 575 (20.5%) 30 (10.8%)\n 30-49 179 (6.4%) 557 (19.9%) 18 (6.5%)\n 50-69 2 (0.1%) 91 (3.2%) 2 (0.7%)\n 70+ 0 (0.0%) 5 (0.2%) 1 (0.4%)\n <NA> 0 (0.0%) 0 (0.0%) 86 (30.9%)\n Total 2,807 (100.0%) 2,803 (100.0%) 278 (100.0%)\n\n\nTo add more complex totals rows that involve summary statistics other than sums, see this section of the Descriptive Tables page.", + "text": "13.5 Counts and tallies\ncount() and tally() provide similar functionality but are different. Read more about the distinction between tally() and count() here.\n\ntally()\ntally() is shorthand for summarise(n = n()), and does not group data. Thus, to achieve grouped tallys it must follow a group_by() command. You can add sort = TRUE to see the largest groups first.\n\nlinelist %>% \n tally()\n\n n\n1 5888\n\n\n\nlinelist %>% \n group_by(outcome) %>% \n tally(sort = TRUE)\n\n# A tibble: 3 × 2\n outcome n\n <chr> <int>\n1 Death 2582\n2 Recover 1983\n3 <NA> 1323\n\n\n\n\ncount()\nIn contrast, count() does the following:\n\napplies group_by() on the specified column(s).\n\napplies summarise() and returns column n with the number of rows per group.\n\napplies ungroup().\n\n\nlinelist %>% \n count(outcome)\n\n outcome n\n1 Death 2582\n2 Recover 1983\n3 <NA> 1323\n\n\nJust like with group_by() you can create a new column within the count() command:\n\nlinelist %>% \n count(age_class = ifelse(age >= 18, \"adult\", \"child\"), sort = T)\n\n age_class n\n1 child 3618\n2 adult 2184\n3 <NA> 86\n\n\ncount() can be called multiple times, with the functionality “rolling up”. For example, to summarise the number of hospitals present for each gender, run the following. Note, the name of the final column is changed from default “n” for clarity (with name =).\n\nlinelist %>% \n # produce counts by unique outcome-gender groups\n count(gender, hospital) %>% \n # gather rows by gender (3) and count number of hospitals per gender (6)\n count(gender, name = \"hospitals per gender\" ) \n\n gender hospitals per gender\n1 f 6\n2 m 6\n3 <NA> 6\n\n\n\n\nAdd counts\nIn contrast to count() and summarise(), you can use add_count() to add a new column n with the counts of rows per group while retaining all the other data frame columns.\nThis means that a group’s count number, in the new column n, will be printed in each row of the group. For demonstration purposes, we add this column and then re-arrange the columns for easier viewing. See the section below on filter on group size for another example.\n\nlinelist %>% \n as_tibble() %>% # convert to tibble for nicer printing \n add_count(hospital) %>% # add column n with counts by hospital\n select(hospital, n, everything()) # re-arrange for demo purposes\n\n# A tibble: 5,888 × 31\n hospital n case_id generation date_infection date_onset\n <chr> <int> <chr> <dbl> <date> <date> \n 1 Other 885 5fe599 4 2014-05-08 2014-05-13\n 2 Missing 1469 8689b7 4 NA 2014-05-13\n 3 St. Mark's Maternity Hosp… 422 11f8ea 2 NA 2014-05-16\n 4 Port Hospital 1762 b8812a 3 2014-05-04 2014-05-18\n 5 Military Hospital 896 893f25 3 2014-05-18 2014-05-21\n 6 Port Hospital 1762 be99c8 3 2014-05-03 2014-05-22\n 7 Missing 1469 07e3e8 4 2014-05-22 2014-05-27\n 8 Missing 1469 369449 4 2014-05-28 2014-06-02\n 9 Missing 1469 f393b4 4 NA 2014-06-05\n10 Missing 1469 1389ca 4 NA 2014-06-05\n# ℹ 5,878 more rows\n# ℹ 25 more variables: date_hospitalisation <date>, date_outcome <date>,\n# outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,\n# age_cat <fct>, age_cat5 <fct>, lon <dbl>, lat <dbl>, infector <chr>,\n# source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>,\n# chills <chr>, cough <chr>, aches <chr>, vomit <chr>, temp <dbl>,\n# time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>\n\n\n\n\nAdd totals\nTo easily add total sum rows or columns after using tally() or count(), see the janitor section of the Descriptive tables page. This package offers functions like adorn_totals() and adorn_percentages() to add totals and convert to show percentages. Below is a brief example:\n\nlinelist %>% # case linelist\n tabyl(age_cat, gender) %>% # cross-tabulate counts of two columns\n adorn_totals(where = \"row\") %>% # add a total row\n adorn_percentages(denominator = \"col\") %>% # convert to proportions with column denominator\n adorn_pct_formatting() %>% # convert proportions to percents\n adorn_ns(position = \"front\") %>% # display as: \"count (percent)\"\n adorn_title( # adjust titles\n row_name = \"Age Category\",\n col_name = \"Gender\")\n\n Gender \n Age Category f m NA_\n 0-4 640 (22.8%) 416 (14.8%) 39 (14.0%)\n 5-9 641 (22.8%) 412 (14.7%) 42 (15.1%)\n 10-14 518 (18.5%) 383 (13.7%) 40 (14.4%)\n 15-19 359 (12.8%) 364 (13.0%) 20 (7.2%)\n 20-29 468 (16.7%) 575 (20.5%) 30 (10.8%)\n 30-49 179 (6.4%) 557 (19.9%) 18 (6.5%)\n 50-69 2 (0.1%) 91 (3.2%) 2 (0.7%)\n 70+ 0 (0.0%) 5 (0.2%) 1 (0.4%)\n <NA> 0 (0.0%) 0 (0.0%) 86 (30.9%)\n Total 2,807 (100.0%) 2,803 (100.0%) 278 (100.0%)\n\n\nTo add more complex totals rows that involve summary statistics other than sums, see this section of the Descriptive Tables page.", "crumbs": [ "Data Management", "13  Grouping data" @@ -1264,7 +1264,7 @@ "href": "new_pages/grouping.html#filter-on-grouped-data", "title": "13  Grouping data", "section": "13.8 Filter on grouped data", - "text": "13.8 Filter on grouped data\n\nfilter()\nWhen applied in conjunction with functions that evaluate the data frame (like max(), min(), mean()), these functions will now be applied to the groups. For example, if you want to filter and keep rows where patients are above the median age, this will now apply per group - filtering to keep rows above the group’s median age.\n\n\nSlice rows per group\nThe dplyr function slice(), which filters rows based on their position in the data, can also be applied per group. Remember to account for sorting the data within each group to get the desired “slice”.\nFor example, to retrieve only the latest 5 admissions from each hospital:\n\nGroup the linelist by column hospital\n\nArrange the records from latest to earliest date_hospitalisation within each hospital group\n\nSlice to retrieve the first 5 rows from each hospital\n\n\nlinelist %>%\n group_by(hospital) %>%\n arrange(hospital, date_hospitalisation) %>%\n slice_head(n = 5) %>% \n arrange(hospital) %>% # for display\n select(case_id, hospital, date_hospitalisation) # for display\n\n# A tibble: 30 × 3\n# Groups: hospital [6]\n case_id hospital date_hospitalisation\n <chr> <chr> <date> \n 1 20b688 Central Hospital 2014-05-06 \n 2 d58402 Central Hospital 2014-05-10 \n 3 b8f2fd Central Hospital 2014-05-13 \n 4 acf422 Central Hospital 2014-05-28 \n 5 275cc7 Central Hospital 2014-05-28 \n 6 d1fafd Military Hospital 2014-04-17 \n 7 974bc1 Military Hospital 2014-05-13 \n 8 6a9004 Military Hospital 2014-05-13 \n 9 09e386 Military Hospital 2014-05-14 \n10 865581 Military Hospital 2014-05-15 \n# ℹ 20 more rows\n\n\nslice_head() - selects n rows from the top\nslice_tail() - selects n rows from the end\nslice_sample() - randomly selects n rows\nslice_min() - selects n rows with highest values in order_by = column, use with_ties = TRUE to keep ties\nslice_max() - selects n rows with lowest values in order_by = column, use with_ties = TRUE to keep ties\nSee the De-duplication page for more examples and detail on slice().\n\n\nFilter on group size\nThe function add_count() adds a column n to the original data giving the number of rows in that row’s group.\nShown below, add_count() is applied to the column hospital, so the values in the new column n reflect the number of rows in that row’s hospital group. Note how values in column n are repeated. In the example below, the column name n could be changed using name = within add_count(). For demonstration purposes we re-arrange the columns with select().\n\nlinelist %>% \n as_tibble() %>% \n add_count(hospital) %>% # add \"number of rows admitted to same hospital as this row\" \n select(hospital, n, everything())\n\n# A tibble: 5,888 × 31\n hospital n case_id generation date_infection date_onset\n <chr> <int> <chr> <dbl> <date> <date> \n 1 Other 885 5fe599 4 2014-05-08 2014-05-13\n 2 Missing 1469 8689b7 4 NA 2014-05-13\n 3 St. Mark's Maternity Hosp… 422 11f8ea 2 NA 2014-05-16\n 4 Port Hospital 1762 b8812a 3 2014-05-04 2014-05-18\n 5 Military Hospital 896 893f25 3 2014-05-18 2014-05-21\n 6 Port Hospital 1762 be99c8 3 2014-05-03 2014-05-22\n 7 Missing 1469 07e3e8 4 2014-05-22 2014-05-27\n 8 Missing 1469 369449 4 2014-05-28 2014-06-02\n 9 Missing 1469 f393b4 4 NA 2014-06-05\n10 Missing 1469 1389ca 4 NA 2014-06-05\n# ℹ 5,878 more rows\n# ℹ 25 more variables: date_hospitalisation <date>, date_outcome <date>,\n# outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,\n# age_cat <fct>, age_cat5 <fct>, lon <dbl>, lat <dbl>, infector <chr>,\n# source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>,\n# chills <chr>, cough <chr>, aches <chr>, vomit <chr>, temp <dbl>,\n# time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>\n\n\nIt then becomes easy to filter for case rows who were hospitalized at a “small” hospital, say, a hospital that admitted fewer than 500 patients:\n\nlinelist %>% \n add_count(hospital) %>% \n filter(n < 500)", + "text": "13.8 Filter on grouped data\n\nfilter()\nWhen applied in conjunction with functions that evaluate the data frame (like max(), min(), mean()), these functions will now be applied to the groups. For example, if you want to filter and keep rows where patients are above the median age, this will now apply per group - filtering to keep rows above the group’s median age.\n\n\nSlice rows per group\nThe dplyr function slice(), which filters rows based on their position in the data, can also be applied per group. Remember to account for sorting the data within each group to get the desired “slice”.\nFor example, to retrieve only the latest 5 admissions from each hospital:\n\nGroup the linelist by column hospital.\n\nArrange the records from latest to earliest date_hospitalisation within each hospital group.\n\nSlice to retrieve the first 5 rows from each hospital.\n\n\nlinelist %>%\n group_by(hospital) %>%\n arrange(hospital, date_hospitalisation) %>%\n slice_head(n = 5) %>% \n arrange(hospital) %>% # for display\n select(case_id, hospital, date_hospitalisation) # for display\n\n# A tibble: 30 × 3\n# Groups: hospital [6]\n case_id hospital date_hospitalisation\n <chr> <chr> <date> \n 1 20b688 Central Hospital 2014-05-06 \n 2 d58402 Central Hospital 2014-05-10 \n 3 b8f2fd Central Hospital 2014-05-13 \n 4 acf422 Central Hospital 2014-05-28 \n 5 275cc7 Central Hospital 2014-05-28 \n 6 d1fafd Military Hospital 2014-04-17 \n 7 974bc1 Military Hospital 2014-05-13 \n 8 6a9004 Military Hospital 2014-05-13 \n 9 09e386 Military Hospital 2014-05-14 \n10 865581 Military Hospital 2014-05-15 \n# ℹ 20 more rows\n\n\nslice_head() - selects n rows from the top.\nslice_tail() - selects n rows from the end.\nslice_sample() - randomly selects n rows.\nslice_min() - selects n rows with highest values in order_by = column, use with_ties = TRUE to keep ties.\nslice_max() - selects n rows with lowest values in order_by = column, use with_ties = TRUE to keep ties.\nSee the De-duplication page for more examples and detail on slice().\n\n\nFilter on group size\nThe function add_count() adds a column n to the original data giving the number of rows in that row’s group.\nShown below, add_count() is applied to the column hospital, so the values in the new column n reflect the number of rows in that row’s hospital group. Note how values in column n are repeated. In the example below, the column name n could be changed using name = within add_count(). For demonstration purposes we re-arrange the columns with select().\n\nlinelist %>% \n as_tibble() %>% \n add_count(hospital) %>% # add \"number of rows admitted to same hospital as this row\" \n select(hospital, n, everything())\n\n# A tibble: 5,888 × 31\n hospital n case_id generation date_infection date_onset\n <chr> <int> <chr> <dbl> <date> <date> \n 1 Other 885 5fe599 4 2014-05-08 2014-05-13\n 2 Missing 1469 8689b7 4 NA 2014-05-13\n 3 St. Mark's Maternity Hosp… 422 11f8ea 2 NA 2014-05-16\n 4 Port Hospital 1762 b8812a 3 2014-05-04 2014-05-18\n 5 Military Hospital 896 893f25 3 2014-05-18 2014-05-21\n 6 Port Hospital 1762 be99c8 3 2014-05-03 2014-05-22\n 7 Missing 1469 07e3e8 4 2014-05-22 2014-05-27\n 8 Missing 1469 369449 4 2014-05-28 2014-06-02\n 9 Missing 1469 f393b4 4 NA 2014-06-05\n10 Missing 1469 1389ca 4 NA 2014-06-05\n# ℹ 5,878 more rows\n# ℹ 25 more variables: date_hospitalisation <date>, date_outcome <date>,\n# outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,\n# age_cat <fct>, age_cat5 <fct>, lon <dbl>, lat <dbl>, infector <chr>,\n# source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>,\n# chills <chr>, cough <chr>, aches <chr>, vomit <chr>, temp <dbl>,\n# time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>\n\n\nIt then becomes easy to filter for case rows who were hospitalized at a “small” hospital, say, a hospital that admitted fewer than 500 patients:\n\nlinelist %>% \n add_count(hospital) %>% \n filter(n < 500)", "crumbs": [ "Data Management", "13  Grouping data" @@ -1275,7 +1275,7 @@ "href": "new_pages/grouping.html#mutate-on-grouped-data", "title": "13  Grouping data", "section": "13.9 Mutate on grouped data", - "text": "13.9 Mutate on grouped data\nTo retain all columns and rows (not summarise) and add a new column containing group statistics, use mutate() after group_by() instead of summarise().\nThis is useful if you want group statistics in the original dataset with all other columns present - e.g. for calculations that compare one row to its group.\nFor example, this code below calculates the difference between a row’s delay-to-admission and the median delay for their hospital. The steps are:\n\nGroup the data by hospital\n\nUse the column days_onset_hosp (delay to hospitalisation) to create a new column containing the mean delay at the hospital of that row\n\nCalculate the difference between the two columns\n\nWe select() only certain columns to display, for demonstration purposes.\n\nlinelist %>% \n # group data by hospital (no change to linelist yet)\n group_by(hospital) %>% \n \n # new columns\n mutate(\n # mean days to admission per hospital (rounded to 1 decimal)\n group_delay_admit = round(mean(days_onset_hosp, na.rm=T), 1),\n \n # difference between row's delay and mean delay at their hospital (rounded to 1 decimal)\n diff_to_group = round(days_onset_hosp - group_delay_admit, 1)) %>%\n \n # select certain rows only - for demonstration/viewing purposes\n select(case_id, hospital, days_onset_hosp, group_delay_admit, diff_to_group)\n\n# A tibble: 5,888 × 5\n# Groups: hospital [6]\n case_id hospital days_onset_hosp group_delay_admit diff_to_group\n <chr> <chr> <dbl> <dbl> <dbl>\n 1 5fe599 Other 2 2 0 \n 2 8689b7 Missing 1 2.1 -1.1\n 3 11f8ea St. Mark's Maternity… 2 2.1 -0.1\n 4 b8812a Port Hospital 2 2.1 -0.1\n 5 893f25 Military Hospital 1 2.1 -1.1\n 6 be99c8 Port Hospital 1 2.1 -1.1\n 7 07e3e8 Missing 2 2.1 -0.1\n 8 369449 Missing 1 2.1 -1.1\n 9 f393b4 Missing 1 2.1 -1.1\n10 1389ca Missing 2 2.1 -0.1\n# ℹ 5,878 more rows", + "text": "13.9 Mutate on grouped data\nTo retain all columns and rows (not summarise) and add a new column containing group statistics, use mutate() after group_by() instead of summarise().\nThis is useful if you want group statistics in the original dataset with all other columns present - e.g. for calculations that compare one row to its group.\nFor example, this code below calculates the difference between a row’s delay-to-admission and the median delay for their hospital. The steps are:\n\nGroup the data by hospital.\n\nUse the column days_onset_hosp (delay to hospitalisation) to create a new column containing the mean delay at the hospital of that row.\n\nCalculate the difference between the two columns.\n\nWe select() only certain columns to display, for demonstration purposes.\n\nlinelist %>% \n # group data by hospital (no change to linelist yet)\n group_by(hospital) %>% \n \n # new columns\n mutate(\n # mean days to admission per hospital (rounded to 1 decimal)\n group_delay_admit = round(mean(days_onset_hosp, na.rm=T), 1),\n \n # difference between row's delay and mean delay at their hospital (rounded to 1 decimal)\n diff_to_group = round(days_onset_hosp - group_delay_admit, 1)) %>%\n \n # select certain rows only - for demonstration/viewing purposes\n select(case_id, hospital, days_onset_hosp, group_delay_admit, diff_to_group)\n\n# A tibble: 5,888 × 5\n# Groups: hospital [6]\n case_id hospital days_onset_hosp group_delay_admit diff_to_group\n <chr> <chr> <dbl> <dbl> <dbl>\n 1 5fe599 Other 2 2 0 \n 2 8689b7 Missing 1 2.1 -1.1\n 3 11f8ea St. Mark's Maternity… 2 2.1 -0.1\n 4 b8812a Port Hospital 2 2.1 -0.1\n 5 893f25 Military Hospital 1 2.1 -1.1\n 6 be99c8 Port Hospital 1 2.1 -1.1\n 7 07e3e8 Missing 2 2.1 -0.1\n 8 369449 Missing 1 2.1 -1.1\n 9 f393b4 Missing 1 2.1 -1.1\n10 1389ca Missing 2 2.1 -0.1\n# ℹ 5,878 more rows", "crumbs": [ "Data Management", "13  Grouping data" @@ -1297,7 +1297,7 @@ "href": "new_pages/grouping.html#resources", "title": "13  Grouping data", "section": "13.11 Resources", - "text": "13.11 Resources\nHere are some useful resources for more information:\nYou can perform any summary function on grouped data; see the RStudio data transformation cheat sheet\nThe Data Carpentry page on dplyr\nThe tidyverse reference pages on group_by() and grouping\nThis page on Data manipulation\nSummarize with conditions in dplyr", + "text": "13.11 Resources\nHere are some useful resources for more information:\nYou can perform any summary function on grouped data; see the RStudio data transformation cheat sheet.\nThe Data Carpentry page on dplyr.\nThe tidyverse reference pages on group_by() and grouping.\nThis page on Data manipulation.\nSummarize with conditions in dplyr.", "crumbs": [ "Data Management", "13  Grouping data" @@ -1319,7 +1319,7 @@ "href": "new_pages/joining_matching.html#preparation", "title": "14  Joining data", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import and export\n here, # locate files \n tidyverse, # data management and visualisation\n RecordLinkage, # probabilistic matches\n fastLink # probabilistic matches\n)\n\n\n\nImport data\nTo begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import case linelist \nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\n\nExample datasets\nIn the joining section below, we will use the following datasets:\n\nA “miniature” version of the case linelist, containing only the columns case_id, date_onset, and hospital, and only the first 10 rows\n\nA separate data frame named hosp_info, which contains more details about each hospital\n\nIn the section on probabilistic matching, we will use two different small datasets. The code to create those datasets is given in that section.\n\n“Miniature” case linelist\nBelow is the the miniature case linelist, which contains only 10 rows and only columns case_id, date_onset, and hospital.\n\nlinelist_mini <- linelist %>% # start with original linelist\n select(case_id, date_onset, hospital) %>% # select columns\n head(10) # only take the first 10 rows\n\n\n\n\n\n\n\n\n\nHospital information data frame\nBelow is the code to create a separate data frame with additional information about seven hospitals (the catchment population, and the level of care available). Note that the name “Military Hospital” belongs to two different hospitals - one a primary level serving 10000 residents and the other a secondary level serving 50280 residents.\n\n# Make the hospital information data frame\nhosp_info = data.frame(\n hosp_name = c(\"central hospital\", \"military\", \"military\", \"port\", \"St. Mark's\", \"ignace\", \"sisters\"),\n catchment_pop = c(1950280, 40500, 10000, 50280, 12000, 5000, 4200),\n level = c(\"Tertiary\", \"Secondary\", \"Primary\", \"Secondary\", \"Secondary\", \"Primary\", \"Primary\")\n)\n\nHere is this data frame:\n\n\n\n\n\n\n\n\n\n\nPre-cleaning\nTraditional joins (non-probabilistic) are case-sensitive and require exact character matches between values in the two data frames. To demonstrate some of the cleaning steps you might need to do before initiating a join, we will clean and align the linelist_mini and hosp_info datasets now.\nIdentify differences\nWe need the values of the hosp_name column in the hosp_info data frame to match the values of the hospital column in the linelist_mini data frame.\nHere are the values in the linelist_mini data frame, printed with the base R function unique():\n\nunique(linelist_mini$hospital)\n\n[1] \"Other\" \n[2] \"Missing\" \n[3] \"St. Mark's Maternity Hospital (SMMH)\"\n[4] \"Port Hospital\" \n[5] \"Military Hospital\" \n\n\nand here are the values in the hosp_info data frame:\n\nunique(hosp_info$hosp_name)\n\n[1] \"central hospital\" \"military\" \"port\" \"St. Mark's\" \n[5] \"ignace\" \"sisters\" \n\n\nYou can see that while some of the hospitals exist in both data frames, there are many differences in spelling.\nAlign values\nWe begin by cleaning the values in the hosp_info data frame. As explained in the Cleaning data and core functions page, we can re-code values with logical criteria using dplyr’s case_when() function. For the four hospitals that exist in both data frames we change the values to align with the values in linelist_mini. The other hospitals we leave the values as they are (TRUE ~ hosp_name).\nCAUTION: Typically when cleaning one should create a new column (e.g. hosp_name_clean), but for ease of demonstration we show modification of the old column\n\nhosp_info <- hosp_info %>% \n mutate(\n hosp_name = case_when(\n # criteria # new value\n hosp_name == \"military\" ~ \"Military Hospital\",\n hosp_name == \"port\" ~ \"Port Hospital\",\n hosp_name == \"St. Mark's\" ~ \"St. Mark's Maternity Hospital (SMMH)\",\n hosp_name == \"central hospital\" ~ \"Central Hospital\",\n TRUE ~ hosp_name\n )\n )\n\nThe hospital names that appear in both data frames are aligned. There are two hospitals in hosp_info that are not present in linelist_mini - we will deal with these later, in the join.\n\nunique(hosp_info$hosp_name)\n\n[1] \"Central Hospital\" \n[2] \"Military Hospital\" \n[3] \"Port Hospital\" \n[4] \"St. Mark's Maternity Hospital (SMMH)\"\n[5] \"ignace\" \n[6] \"sisters\" \n\n\nPrior to a join, it is often easiest to convert a column to all lowercase or all uppercase. If you need to convert all values in a column to UPPER or lower case, use mutate() and wrap the column with one of these functions from stringr, as shown in the page on Characters and strings.\nstr_to_upper()\nstr_to_upper()\nstr_to_title()", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import and export\n here, # locate files \n tidyverse, # data management and visualisation\n RecordLinkage, # probabilistic matches\n fastLink # probabilistic matches\n)\n\n\n\nImport data\nTo begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import case linelist \nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\n\nExample datasets\nIn the joining section below, we will use the following datasets:\n\nA “miniature” version of the case linelist, containing only the columns case_id, date_onset, and hospital, and only the first 10 rows.\n\nA separate data frame named hosp_info, which contains more details about each hospital.\n\nIn the section on probabilistic matching, we will use two different small datasets. The code to create those datasets is given in that section.\n\n“Miniature” case linelist\nBelow is the the miniature case linelist, which contains only 10 rows and only columns case_id, date_onset, and hospital.\n\nlinelist_mini <- linelist %>% # start with original linelist\n select(case_id, date_onset, hospital) %>% # select columns\n head(10) # only take the first 10 rows\n\n\n\n\n\n\n\n\n\nHospital information data frame\nBelow is the code to create a separate data frame with additional information about seven hospitals (the catchment population, and the level of care available). Note that the name “Military Hospital” belongs to two different hospitals - one a primary level serving 10000 residents and the other a secondary level serving 50280 residents.\n\n# Make the hospital information data frame\nhosp_info = data.frame(\n hosp_name = c(\"central hospital\", \"military\", \"military\", \"port\", \"St. Mark's\", \"ignace\", \"sisters\"),\n catchment_pop = c(1950280, 40500, 10000, 50280, 12000, 5000, 4200),\n level = c(\"Tertiary\", \"Secondary\", \"Primary\", \"Secondary\", \"Secondary\", \"Primary\", \"Primary\")\n)\n\nHere is this data frame:\n\n\n\n\n\n\n\n\n\n\nPre-cleaning\nTraditional joins (non-probabilistic) are case-sensitive and require exact character matches between values in the two data frames. To demonstrate some of the cleaning steps you might need to do before initiating a join, we will clean and align the linelist_mini and hosp_info datasets now.\nIdentify differences\nWe need the values of the hosp_name column in the hosp_info data frame to match the values of the hospital column in the linelist_mini data frame.\nHere are the values in the linelist_mini data frame, printed with the base R function unique():\n\nunique(linelist_mini$hospital)\n\n[1] \"Other\" \n[2] \"Missing\" \n[3] \"St. Mark's Maternity Hospital (SMMH)\"\n[4] \"Port Hospital\" \n[5] \"Military Hospital\" \n\n\nand here are the values in the hosp_info data frame:\n\nunique(hosp_info$hosp_name)\n\n[1] \"central hospital\" \"military\" \"port\" \"St. Mark's\" \n[5] \"ignace\" \"sisters\" \n\n\nYou can see that while some of the hospitals exist in both data frames, there are many differences in spelling.\nAlign values\nWe begin by cleaning the values in the hosp_info data frame. As explained in the Cleaning data and core functions page, we can re-code values with logical criteria using dplyr’s case_when() function. For the four hospitals that exist in both data frames we change the values to align with the values in linelist_mini. The other hospitals we leave the values as they are (TRUE ~ hosp_name).\nCAUTION: Typically when cleaning one should create a new column (e.g. hosp_name_clean), but for ease of demonstration we show modification of the old column\n\nhosp_info <- hosp_info %>% \n mutate(\n hosp_name = case_when(\n # criteria # new value\n hosp_name == \"military\" ~ \"Military Hospital\",\n hosp_name == \"port\" ~ \"Port Hospital\",\n hosp_name == \"St. Mark's\" ~ \"St. Mark's Maternity Hospital (SMMH)\",\n hosp_name == \"central hospital\" ~ \"Central Hospital\",\n TRUE ~ hosp_name\n )\n )\n\nThe hospital names that appear in both data frames are aligned. There are two hospitals in hosp_info that are not present in linelist_mini - we will deal with these later, in the join.\n\nunique(hosp_info$hosp_name)\n\n[1] \"Central Hospital\" \n[2] \"Military Hospital\" \n[3] \"Port Hospital\" \n[4] \"St. Mark's Maternity Hospital (SMMH)\"\n[5] \"ignace\" \n[6] \"sisters\" \n\n\nPrior to a join, it is often easiest to convert a column to all lowercase or all uppercase. If you need to convert all values in a column to UPPER or lower case, use mutate() and wrap the column with one of these functions from stringr, as shown in the page on Characters and strings.\nstr_to_upper()\nstr_to_upper()\nstr_to_title()", "crumbs": [ "Data Management", "14  Joining data" @@ -1330,7 +1330,7 @@ "href": "new_pages/joining_matching.html#dplyr-joins", "title": "14  Joining data", "section": "14.2 dplyr joins", - "text": "14.2 dplyr joins\nThe dplyr package offers several different join functions. dplyr is included in the tidyverse package. These join functions are described below, with simple use cases.\nMany thanks to https://github.com/gadenbuie for the informative gifs!\n\n\nGeneral syntax\nThe join commands can be run as standalone commands to join two data frames into a new object, or they can be used within a pipe chain (%>%) to merge one data frame into another as it is being cleaned or otherwise modified.\nIn the example below, the function left_join() is used as a standalone command to create the a new joined_data data frame. The inputs are data frames 1 and 2 (df1 and df2). The first data frame listed is the baseline data frame, and the second one listed is joined to it.\nThe third argument by = is where you specify the columns in each data frame that will be used to aligns the rows in the two data frames. If the names of these columns are different, provide them within a c() vector as shown below, where the rows are matched on the basis of common values between the column ID in df1 and the column identifier in df2.\n\n# Join based on common values between column \"ID\" (first data frame) and column \"identifier\" (second data frame)\njoined_data <- left_join(df1, df2, by = c(\"ID\" = \"identifier\"))\n\nIf the by columns in both data frames have the exact same name, you can just provide this one name, within quotes.\n\n# Joint based on common values in column \"ID\" in both data frames\njoined_data <- left_join(df1, df2, by = \"ID\")\n\nIf you are joining the data frames based on common values across multiple fields, list these fields within the c() vector. This example joins rows if the values in three columns in each dataset align exactly.\n\n# join based on same first name, last name, and age\njoined_data <- left_join(df1, df2, by = c(\"name\" = \"firstname\", \"surname\" = \"lastname\", \"Age\" = \"age\"))\n\nThe join commands can also be run within a pipe chain. This will modify the data frame being piped.\nIn the example below, df1 is is passed through the pipes, df2 is joined to it, and df is thus modified and re-defined.\n\ndf1 <- df1 %>%\n filter(date_onset < as.Date(\"2020-03-05\")) %>% # miscellaneous cleaning \n left_join(df2, by = c(\"ID\" = \"identifier\")) # join df2 to df1\n\nCAUTION: Joins are case-specific! Therefore it is useful to convert all values to lowercase or uppercase prior to joining. See the page on characters/strings.\n\n\n\nLeft and right joins\nA left or right join is commonly used to add information to a data frame - new information is added only to rows that already existed in the baseline data frame. These are common joins in epidemiological work as they are used to add information from one dataset into another.\nIn using these joins, the written order of the data frames in the command is important*.\n\nIn a left join, the first data frame written is the baseline\n\nIn a right join, the second data frame written is the baseline\n\nAll rows of the baseline data frame are kept. Information in the other (secondary) data frame is joined to the baseline data frame only if there is a match via the identifier column(s). In addition:\n\nRows in the secondary data frame that do not match are dropped.\n\nIf there are many baseline rows that match to one row in the secondary data frame (many-to-one), the secondary information is added to each matching baseline row.\n\nIf a baseline row matches to multiple rows in the secondary data frame (one-to-many), all combinations are given, meaning new rows may be added to your returned data frame!\n\nAnimated examples of left and right joins (image source)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nExample\nBelow is the output of a left_join() of hosp_info (secondary data frame, view here) into linelist_mini (baseline data frame, view here). The original linelist_mini has nrow(linelist_mini) rows. The modified linelist_mini is displayed. Note the following:\n\nTwo new columns, catchment_pop and level have been added on the left side of linelist_mini\n\nAll original rows of the baseline data frame linelist_mini are kept\n\nAny original rows of linelist_mini for “Military Hospital” are duplicated because it matched to two rows in the secondary data frame, so both combinations are returned\n\nThe join identifier column of the secondary dataset (hosp_name) has disappeared because it is redundant with the identifier column in the primary dataset (hospital)\n\nWhen a baseline row did not match to any secondary row (e.g. when hospital is “Other” or “Missing”), NA (blank) fills in the columns from the secondary data frame\n\nRows in the secondary data frame with no match to the baseline data frame (“sisters” and “ignace” hospitals) were dropped\n\n\nlinelist_mini %>% \n left_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in left_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n“Should I use a right join, or a left join?”\nTo answer the above question, ask yourself “which data frame should retain all of its rows?” - use this one as the baseline. A left join keep all the rows in the first data frame written in the command, whereas a right join keeps all the rows in the second data frame.\nThe two commands below achieve the same output - 10 rows of hosp_info joined into a linelist_mini baseline, but they use different joins. The result is that the column order will differ based on whether hosp_info arrives from the right (in the left join) or arrives from the left (in the right join). The order of the rows may also shift accordingly. But both of these consequences can be subsequently addressed, using select() to re-order columns or arrange() to sort rows.\n\n# The two commands below achieve the same data, but with differently ordered rows and columns\nleft_join(linelist_mini, hosp_info, by = c(\"hospital\" = \"hosp_name\"))\nright_join(hosp_info, linelist_mini, by = c(\"hosp_name\" = \"hospital\"))\n\nHere is the result of hosp_info into linelist_mini via a left join (new columns incoming from the right)\n\n\nWarning in left_join(linelist_mini, hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\nHere is the result of hosp_info into linelist_mini via a right join (new columns incoming from the left)\n\n\nWarning in right_join(hosp_info, linelist_mini, by = c(hosp_name = \"hospital\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 4 of `x` matches multiple rows in `y`.\nℹ Row 5 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\nAlso consider whether your use-case is within a pipe chain (%>%). If the dataset in the pipes is the baseline, you will likely use a left join to add data to it.\n\n\n\n\nFull join\nA full join is the most inclusive of the joins - it returns all rows from both data frames.\nIf there are any rows present in one and not the other (where no match was found), the data frame will include them and become longer. NA missing values are used to fill-in any gaps created. As you join, watch the number of columns and rows carefully to troubleshoot case-sensitivity and exact character matches.\nThe “baseline” data frame is the one written first in the command. Adjustment of this will not impact which records are returned by the join, but it can impact the resulting column order, row order, and which identifier columns are retained.\n\n\n\n\n\n\n\n\n\nAnimated example of a full join (image source)\nExample\nBelow is the output of a full_join() of hosp_info (originally nrow(hosp_info), view here) into linelist_mini (originally nrow(linelist_mini), view here). Note the following:\n\nAll baseline rows are kept (linelist_mini)\n\nRows in the secondary that do not match to the baseline are kept (“ignace” and “sisters”), with values in the corresponding baseline columns case_id and onset filled in with missing values\n\nLikewise, rows in the baseline data frame that do not match to the secondary (“Other” and “Missing”) are kept, with secondary columns catchment_pop and level filled-in with missing values\n\nIn the case of one-to-many or many-to-one matches (e.g. rows for “Military Hospital”), all possible combinations are returned (lengthening the final data frame)\n\nOnly the identifier column from the baseline is kept (hospital)\n\n\nlinelist_mini %>% \n full_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in full_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n\n\nInner join\nAn inner join is the most restrictive of the joins - it returns only rows with matches across both data frames.\nThis means that the number of rows in the baseline data frame may actually reduce. Adjustment of which data frame is the “baseline” (written first in the function) will not impact which rows are returned, but it will impact the column order, row order, and which identifier columns are retained.\n\n\n\n\n\n\n\n\n\nAnimated example of an inner join (image source)\nExample\nBelow is the output of an inner_join() of linelist_mini (baseline) with hosp_info (secondary). Note the following:\n\nBaseline rows with no match to the secondary data are removed (rows where hospital is “Missing” or “Other”)\n\nLikewise, rows from the secondary data frame that had no match in the baseline are removed (rows where hosp_name is “sisters” or “ignace”)\n\nOnly the identifier column from the baseline is kept (hospital)\n\n\nlinelist_mini %>% \n inner_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in inner_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n\n\nSemi join\nA semi join is a “filtering join” which uses another dataset not to add rows or columns, but to perform filtering.\nA semi-join keeps all observations in the baseline data frame that have a match in the secondary data frame (but does not add new columns nor duplicate any rows for multiple matches). Read more about these “filtering” joins here.\n\n\n\n\n\n\n\n\n\nAnimated example of a semi join (image source)\nAs an example, the below code returns rows from the hosp_info data frame that have matches in linelist_mini based on hospital name.\n\nhosp_info %>% \n semi_join(linelist_mini, by = c(\"hosp_name\" = \"hospital\"))\n\n hosp_name catchment_pop level\n1 Military Hospital 40500 Secondary\n2 Military Hospital 10000 Primary\n3 Port Hospital 50280 Secondary\n4 St. Mark's Maternity Hospital (SMMH) 12000 Secondary\n\n\n\n\n\nAnti join\nThe anti join is another “filtering join” that returns rows in the baseline data frame that do not have a match in the secondary data frame.\nRead more about filtering joins here.\nCommon scenarios for an anti-join include identifying records not present in another data frame, troubleshooting spelling in a join (reviewing records that should have matched), and examining records that were excluded after another join.\nAs with right_join() and left_join(), the baseline data frame (listed first) is important. The returned rows are from the baseline data frame only. Notice in the gif below that row in the secondary data frame (purple row 4) is not returned even though it does not match with the baseline.\n\n\n\n\n\n\n\n\n\nAnimated example of an anti join (image source)\n\nSimple anti_join() example\nFor a simple example, let’s find the hosp_info hospitals that do not have any cases present in linelist_mini. We list hosp_info first, as the baseline data frame. The hospitals which are not present in linelist_mini are returned.\n\nhosp_info %>% \n anti_join(linelist_mini, by = c(\"hosp_name\" = \"hospital\"))\n\n\n\n\n\n\n\n\n\nComplex anti_join() example\nFor another example, let us say we ran an inner_join() between linelist_mini and hosp_info. This returns only a subset of the original linelist_mini records, as some are not present in hosp_info.\n\nlinelist_mini %>% \n inner_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in inner_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\nTo review the linelist_mini records that were excluded during the inner join, we can run an anti-join with the same settings (linelist_mini as the baseline).\n\nlinelist_mini %>% \n anti_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\n\n\n\n\nTo see the hosp_info records that were excluded in the inner join, we could also run an anti-join with hosp_info as the baseline data frame.", + "text": "14.2 dplyr joins\nThe dplyr package offers several different join functions. dplyr is included in the tidyverse package. These join functions are described below, with simple use cases.\nMany thanks to https://github.com/gadenbuie for the informative gifs!\n\n\nGeneral syntax\nThe join commands can be run as standalone commands to join two data frames into a new object, or they can be used within a pipe chain (%>%) to merge one data frame into another as it is being cleaned or otherwise modified.\nIn the example below, the function left_join() is used as a standalone command to create the a new joined_data data frame. The inputs are data frames 1 and 2 (df1 and df2). The first data frame listed is the baseline data frame, and the second one listed is joined to it.\nThe third argument by = is where you specify the columns in each data frame that will be used to aligns the rows in the two data frames. If the names of these columns are different, provide them within a c() vector as shown below, where the rows are matched on the basis of common values between the column ID in df1 and the column identifier in df2.\n\n# Join based on common values between column \"ID\" (first data frame) and column \"identifier\" (second data frame)\njoined_data <- left_join(df1, df2, by = c(\"ID\" = \"identifier\"))\n\nIf the by columns in both data frames have the exact same name, you can just provide this one name, within quotes.\n\n# Joint based on common values in column \"ID\" in both data frames\njoined_data <- left_join(df1, df2, by = \"ID\")\n\nIf you are joining the data frames based on common values across multiple fields, list these fields within the c() vector. This example joins rows if the values in three columns in each dataset align exactly.\n\n# join based on same first name, last name, and age\njoined_data <- left_join(df1, df2, by = c(\"name\" = \"firstname\", \"surname\" = \"lastname\", \"Age\" = \"age\"))\n\nThe join commands can also be run within a pipe chain. This will modify the data frame being piped.\nIn the example below, df1 is is passed through the pipes, df2 is joined to it, and df is thus modified and re-defined.\n\ndf1 <- df1 %>%\n filter(date_onset < as.Date(\"2020-03-05\")) %>% # miscellaneous cleaning \n left_join(df2, by = c(\"ID\" = \"identifier\")) # join df2 to df1\n\nCAUTION: Joins are case-specific! Therefore it is useful to convert all values to lowercase or uppercase prior to joining. See the page on characters/strings.\n\n\n\nLeft and right joins\nA left or right join is commonly used to add information to a data frame - new information is added only to rows that already existed in the baseline data frame. These are common joins in epidemiological work as they are used to add information from one dataset into another.\nIn using these joins, the written order of the data frames in the command is important.\n\nIn a left join, the first data frame written is the baseline.\n\nIn a right join, the second data frame written is the baseline.\n\nAll rows of the baseline data frame are kept. Information in the other (secondary) data frame is joined to the baseline data frame only if there is a match via the identifier column(s). In addition:\n\nRows in the secondary data frame that do not match are dropped.\n\nIf there are many baseline rows that match to one row in the secondary data frame (many-to-one), the secondary information is added to each matching baseline row.\n\nIf a baseline row matches to multiple rows in the secondary data frame (one-to-many), all combinations are given, meaning new rows may be added to your returned data frame!.\n\nAnimated examples of left and right joins (image source)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nExample\nBelow is the output of a left_join() of hosp_info (secondary data frame, view here) into linelist_mini (baseline data frame, view here). The original linelist_mini has nrow(linelist_mini) rows. The modified linelist_mini is displayed. Note the following:\n\nTwo new columns, catchment_pop and level have been added on the left side of linelist_mini.\n\nAll original rows of the baseline data frame linelist_mini are kept.\n\nAny original rows of linelist_mini for “Military Hospital” are duplicated because it matched to two rows in the secondary data frame, so both combinations are returned.\n\nThe join identifier column of the secondary dataset (hosp_name) has disappeared because it is redundant with the identifier column in the primary dataset (hospital).\n\nWhen a baseline row did not match to any secondary row (e.g. when hospital is “Other” or “Missing”), NA (blank) fills in the columns from the secondary data frame.\n\nRows in the secondary data frame with no match to the baseline data frame (“sisters” and “ignace” hospitals) were dropped.\n\n\nlinelist_mini %>% \n left_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in left_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n“Should I use a right join, or a left join?”\nTo answer the above question, ask yourself “which data frame should retain all of its rows?” - use this one as the baseline. A left join keep all the rows in the first data frame written in the command, whereas a right join keeps all the rows in the second data frame.\nThe two commands below achieve the same output - 10 rows of hosp_info joined into a linelist_mini baseline, but they use different joins. The result is that the column order will differ based on whether hosp_info arrives from the right (in the left join) or arrives from the left (in the right join). The order of the rows may also shift accordingly. But both of these consequences can be subsequently addressed, using select() to re-order columns or arrange() to sort rows.\n\n# The two commands below achieve the same data, but with differently ordered rows and columns\nleft_join(linelist_mini, hosp_info, by = c(\"hospital\" = \"hosp_name\"))\nright_join(hosp_info, linelist_mini, by = c(\"hosp_name\" = \"hospital\"))\n\nHere is the result of hosp_info into linelist_mini via a left join (new columns incoming from the right)\n\n\nWarning in left_join(linelist_mini, hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\nHere is the result of hosp_info into linelist_mini via a right join (new columns incoming from the left)\n\n\nWarning in right_join(hosp_info, linelist_mini, by = c(hosp_name = \"hospital\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 4 of `x` matches multiple rows in `y`.\nℹ Row 5 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\nAlso consider whether your use-case is within a pipe chain (%>%). If the dataset in the pipes is the baseline, you will likely use a left join to add data to it.\n\n\n\n\nFull join\nA full join is the most inclusive of the joins - it returns all rows from both data frames.\nIf there are any rows present in one and not the other (where no match was found), the data frame will include them and become longer. NA missing values are used to fill-in any gaps created. As you join, watch the number of columns and rows carefully to troubleshoot case-sensitivity and exact character matches.\nThe “baseline” data frame is the one written first in the command. Adjustment of this will not impact which records are returned by the join, but it can impact the resulting column order, row order, and which identifier columns are retained.\n\n\n\n\n\n\n\n\n\nAnimated example of a full join (image source)\nExample\nBelow is the output of a full_join() of hosp_info (originally nrow(hosp_info), view here) into linelist_mini (originally nrow(linelist_mini), view here). Note the following:\n\nAll baseline rows are kept (linelist_mini).\n\nRows in the secondary that do not match to the baseline are kept (“ignace” and “sisters”), with values in the corresponding baseline columns case_id and onset filled in with missing values.\n\nLikewise, rows in the baseline data frame that do not match to the secondary (“Other” and “Missing”) are kept, with secondary columns catchment_pop and level filled-in with missing values.\n\nIn the case of one-to-many or many-to-one matches (e.g. rows for “Military Hospital”), all possible combinations are returned (lengthening the final data frame).\n\nOnly the identifier column from the baseline is kept (hospital).\n\n\nlinelist_mini %>% \n full_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in full_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n\n\nInner join\nAn inner join is the most restrictive of the joins - it returns only rows with matches across both data frames.\nThis means that the number of rows in the baseline data frame may actually reduce. Adjustment of which data frame is the “baseline” (written first in the function) will not impact which rows are returned, but it will impact the column order, row order, and which identifier columns are retained.\n\n\n\n\n\n\n\n\n\nAnimated example of an inner join (image source)\nExample\nBelow is the output of an inner_join() of linelist_mini (baseline) with hosp_info (secondary). Note the following:\n\nBaseline rows with no match to the secondary data are removed (rows where hospital is “Missing” or “Other”).\n\nLikewise, rows from the secondary data frame that had no match in the baseline are removed (rows where hosp_name is “sisters” or “ignace”).\n\nOnly the identifier column from the baseline is kept (hospital).\n\n\nlinelist_mini %>% \n inner_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in inner_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n\n\nSemi join\nA semi join is a “filtering join” which uses another dataset not to add rows or columns, but to perform filtering.\nA semi-join keeps all observations in the baseline data frame that have a match in the secondary data frame (but does not add new columns nor duplicate any rows for multiple matches). Read more about these “filtering” joins here.\n\n\n\n\n\n\n\n\n\nAnimated example of a semi join (image source)\nAs an example, the below code returns rows from the hosp_info data frame that have matches in linelist_mini based on hospital name.\n\nhosp_info %>% \n semi_join(linelist_mini, by = c(\"hosp_name\" = \"hospital\"))\n\n hosp_name catchment_pop level\n1 Military Hospital 40500 Secondary\n2 Military Hospital 10000 Primary\n3 Port Hospital 50280 Secondary\n4 St. Mark's Maternity Hospital (SMMH) 12000 Secondary\n\n\n\n\n\nAnti join\nThe anti join is another “filtering join” that returns rows in the baseline data frame that do not have a match in the secondary data frame.\nRead more about filtering joins here.\nCommon scenarios for an anti-join include identifying records not present in another data frame, troubleshooting spelling in a join (reviewing records that should have matched), and examining records that were excluded after another join.\nAs with right_join() and left_join(), the baseline data frame (listed first) is important. The returned rows are from the baseline data frame only. Notice in the gif below that row in the secondary data frame (purple row 4) is not returned even though it does not match with the baseline.\n\n\n\n\n\n\n\n\n\nAnimated example of an anti join (image source)\n\nSimple anti_join() example\nFor a simple example, let’s find the hosp_info hospitals that do not have any cases present in linelist_mini. We list hosp_info first, as the baseline data frame. The hospitals which are not present in linelist_mini are returned.\n\nhosp_info %>% \n anti_join(linelist_mini, by = c(\"hosp_name\" = \"hospital\"))\n\n\n\n\n\n\n\n\n\nComplex anti_join() example\nFor another example, let us say we ran an inner_join() between linelist_mini and hosp_info. This returns only a subset of the original linelist_mini records, as some are not present in hosp_info.\n\nlinelist_mini %>% \n inner_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\nWarning in inner_join(., hosp_info, by = c(hospital = \"hosp_name\")): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 5 of `x` matches multiple rows in `y`.\nℹ Row 4 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\nTo review the linelist_mini records that were excluded during the inner join, we can run an anti-join with the same settings (linelist_mini as the baseline).\n\nlinelist_mini %>% \n anti_join(hosp_info, by = c(\"hospital\" = \"hosp_name\"))\n\n\n\n\n\n\n\nTo see the hosp_info records that were excluded in the inner join, we could also run an anti-join with hosp_info as the baseline data frame.", "crumbs": [ "Data Management", "14  Joining data" @@ -1341,7 +1341,7 @@ "href": "new_pages/joining_matching.html#probabalistic-matching", "title": "14  Joining data", "section": "14.3 Probabalistic matching", - "text": "14.3 Probabalistic matching\nIf you do not have a unique identifier common across datasets to join on, consider using a probabilistic matching algorithm. This would find matches between records based on similarity (e.g. Jaro–Winkler string distance, or numeric distance). Below is a simple example using the package fastLink .\nLoad packages\n\npacman::p_load(\n tidyverse, # data manipulation and visualization\n fastLink # record matching\n )\n\nHere are two small example datasets that we will use to demonstrate the probabilistic matching (cases and test_results):\nHere is the code used to make the datasets:\n\n# make datasets\n\ncases <- tribble(\n ~gender, ~first, ~middle, ~last, ~yr, ~mon, ~day, ~district,\n \"M\", \"Amir\", NA, \"Khan\", 1989, 11, 22, \"River\",\n \"M\", \"Anthony\", \"B.\", \"Smith\", 1970, 09, 19, \"River\", \n \"F\", \"Marialisa\", \"Contreras\", \"Rodrigues\", 1972, 04, 15, \"River\",\n \"F\", \"Elizabeth\", \"Casteel\", \"Chase\", 1954, 03, 03, \"City\",\n \"M\", \"Jose\", \"Sanchez\", \"Lopez\", 1996, 01, 06, \"City\",\n \"F\", \"Cassidy\", \"Jones\", \"Davis\", 1980, 07, 20, \"City\",\n \"M\", \"Michael\", \"Murphy\", \"O'Calaghan\",1969, 04, 12, \"Rural\", \n \"M\", \"Oliver\", \"Laurent\", \"De Bordow\" , 1971, 02, 04, \"River\",\n \"F\", \"Blessing\", NA, \"Adebayo\", 1955, 02, 14, \"Rural\"\n)\n\nresults <- tribble(\n ~gender, ~first, ~middle, ~last, ~yr, ~mon, ~day, ~district, ~result,\n \"M\", \"Amir\", NA, \"Khan\", 1989, 11, 22, \"River\", \"positive\",\n \"M\", \"Tony\", \"B\", \"Smith\", 1970, 09, 19, \"River\", \"positive\",\n \"F\", \"Maria\", \"Contreras\", \"Rodriguez\", 1972, 04, 15, \"Cty\", \"negative\",\n \"F\", \"Betty\", \"Castel\", \"Chase\", 1954, 03, 30, \"City\", \"positive\",\n \"F\", \"Andrea\", NA, \"Kumaraswamy\", 2001, 01, 05, \"Rural\", \"positive\", \n \"F\", \"Caroline\", NA, \"Wang\", 1988, 12, 11, \"Rural\", \"negative\",\n \"F\", \"Trang\", NA, \"Nguyen\", 1981, 06, 10, \"Rural\", \"positive\",\n \"M\", \"Olivier\" , \"Laurent\", \"De Bordeaux\", NA, NA, NA, \"River\", \"positive\",\n \"M\", \"Mike\", \"Murphy\", \"O'Callaghan\", 1969, 04, 12, \"Rural\", \"negative\",\n \"F\", \"Cassidy\", \"Jones\", \"Davis\", 1980, 07, 02, \"City\", \"positive\",\n \"M\", \"Mohammad\", NA, \"Ali\", 1942, 01, 17, \"City\", \"negative\",\n NA, \"Jose\", \"Sanchez\", \"Lopez\", 1995, 01, 06, \"City\", \"negative\",\n \"M\", \"Abubakar\", NA, \"Abullahi\", 1960, 01, 01, \"River\", \"positive\",\n \"F\", \"Maria\", \"Salinas\", \"Contreras\", 1955, 03, 03, \"River\", \"positive\"\n )\n\nThe cases dataset has 9 records of patients who are awaiting test results.\n\n\n\n\n\n\nThe test_results dataset has 14 records and contains the column result, which we want to add to the records in cases based on probabilistic matching of records.\n\n\n\n\n\n\n\nProbabilistic matching\nThe fastLink() function from the fastLink package can be used to apply a matching algorithm. Here is the basic information. You can read more detail by entering ?fastLink in your console.\n\nDefine the two data frames for comparison to arguments dfA = and dfB =\n\nIn varnames = give all column names to be used for matching. They must all exist in both dfA and dfB.\n\nIn stringdist.match = give columns from those in varnames to be evaluated on string “distance”.\n\nIn numeric.match = give columns from those in varnames to be evaluated on numeric distance.\n\nMissing values are ignored\n\nBy default, each row in either data frame is matched to at most one row in the other data frame. If you want to see all the evaluated matches, set dedupe.matches = FALSE. The deduplication is done using Winkler’s linear assignment solution.\n\nTip: split one date column into three separate numeric columns using day(), month(), and year() from lubridate package\nThe default threshold for matches is 0.94 (threshold.match =) but you can adjust it higher or lower. If you define the threshold, consider that higher thresholds could yield more false-negatives (rows that do not match which actually should match) and likewise a lower threshold could yield more false-positive matches.\nBelow, the data are matched on string distance across the name and district columns, and on numeric distance for year, month, and day of birth. A match threshold of 95% probability is set.\n\nfl_output <- fastLink::fastLink(\n dfA = cases,\n dfB = results,\n varnames = c(\"gender\", \"first\", \"middle\", \"last\", \"yr\", \"mon\", \"day\", \"district\"),\n stringdist.match = c(\"first\", \"middle\", \"last\", \"district\"),\n numeric.match = c(\"yr\", \"mon\", \"day\"),\n threshold.match = 0.95)\n\n\n==================== \nfastLink(): Fast Probabilistic Record Linkage\n==================== \n\nIf you set return.all to FALSE, you will not be able to calculate a confusion table as a summary statistic.\nCalculating matches for each variable.\nGetting counts for parameter estimation.\n Parallelizing calculation using OpenMP. 1 threads out of 8 are used.\nRunning the EM algorithm.\nGetting the indices of estimated matches.\n Parallelizing calculation using OpenMP. 1 threads out of 8 are used.\nDeduping the estimated matches.\nGetting the match patterns for each estimated match.\n\n\nReview matches\nWe defined the object returned from fastLink() as fl_output. It is of class list, and it actually contains several data frames within it, detailing the results of the matching. One of these data frames is matches, which contains the most likely matches across cases and results. You can access this “matches” data frame with fl_output$matches. Below, it is saved as my_matches for ease of accessing later.\nWhen my_matches is printed, you see two column vectors: the pairs of row numbers/indices (also called “rownames”) in cases (“inds.a”) and in results (“inds.b”) representing the best matches. If a row number from a datafrane is missing, then no match was found in the other data frame at the specified match threshold.\n\n# print matches\nmy_matches <- fl_output$matches\nmy_matches\n\n inds.a inds.b\n1 1 1\n2 2 2\n3 3 3\n4 4 4\n5 8 8\n6 7 9\n7 6 10\n8 5 12\n\n\nThings to note:\n\nMatches occurred despite slight differences in name spelling and dates of birth:\n\n“Tony B. Smith” matched to “Anthony B Smith”\n\n“Maria Rodriguez” matched to “Marialisa Rodrigues”\n\n“Betty Chase” matched to “Elizabeth Chase”\n\n“Olivier Laurent De Bordeaux” matched to “Oliver Laurent De Bordow” (missing date of birth ignored)\n\n\nOne row from cases (for “Blessing Adebayo”, row 9) had no good match in results, so it is not present in my_matches.\n\nJoin based on the probabilistic matches\nTo use these matches to join results to cases, one strategy is:\n\nUse left_join() to join my_matches to cases (matching rownames in cases to “inds.a” in my_matches)\n\nThen use another left_join() to join results to cases (matching the newly-acquired “inds.b” in cases to rownames in results)\n\nBefore the joins, we should clean the three data frames:\n\nBoth dfA and dfB should have their row numbers (“rowname”) converted to a proper column.\n\nBoth the columns in my_matches are converted to class character, so they can be joined to the character rownames\n\n\n# Clean data prior to joining\n#############################\n\n# convert cases rownames to a column \ncases_clean <- cases %>% rownames_to_column()\n\n# convert test_results rownames to a column\nresults_clean <- results %>% rownames_to_column() \n\n# convert all columns in matches dataset to character, so they can be joined to the rownames\nmatches_clean <- my_matches %>%\n mutate(across(everything(), as.character))\n\n\n\n# Join matches to dfA, then add dfB\n###################################\n# column \"inds.b\" is added to dfA\ncomplete <- left_join(cases_clean, matches_clean, by = c(\"rowname\" = \"inds.a\"))\n\n# column(s) from dfB are added \ncomplete <- left_join(complete, results_clean, by = c(\"inds.b\" = \"rowname\"))\n\nAs performed using the code above, the resulting data frame complete will contain all columns from both cases and results. Many will be appended with suffixes “.x” and “.y”, because the column names would otherwise be duplicated.\n\n\n\n\n\n\nAlternatively, to achieve only the “original” 9 records in cases with the new column(s) from results, use select() on results before the joins, so that it contains only rownames and the columns that you want to add to cases (e.g. the column result).\n\ncases_clean <- cases %>% rownames_to_column()\n\nresults_clean <- results %>%\n rownames_to_column() %>% \n select(rowname, result) # select only certain columns \n\nmatches_clean <- my_matches %>%\n mutate(across(everything(), as.character))\n\n# joins\ncomplete <- left_join(cases_clean, matches_clean, by = c(\"rowname\" = \"inds.a\"))\ncomplete <- left_join(complete, results_clean, by = c(\"inds.b\" = \"rowname\"))\n\n\n\n\n\n\n\nIf you want to subset either dataset to only the rows that matched, you can use the codes below:\n\ncases_matched <- cases[my_matches$inds.a,] # Rows in cases that matched to a row in results\nresults_matched <- results[my_matches$inds.b,] # Rows in results that matched to a row in cases\n\nOr, to see only the rows that did not match:\n\ncases_not_matched <- cases[!rownames(cases) %in% my_matches$inds.a,] # Rows in cases that did NOT match to a row in results\nresults_not_matched <- results[!rownames(results) %in% my_matches$inds.b,] # Rows in results that did NOT match to a row in cases\n\n\n\nProbabilistic deduplication\nProbabilistic matching can be used to deduplicate a dataset as well. See the page on deduplication for other methods of deduplication.\nHere we began with the cases dataset, but are now calling it cases_dup, as it has 2 additional rows that could be duplicates of previous rows: See “Tony” with “Anthony”, and “Marialisa Rodrigues” with “Maria Rodriguez”.\n\n\n\n\n\n\nRun fastLink() like before, but compare the cases_dup data frame to itself. When the two data frames provided are identical, the function assumes you want to de-duplicate. Note we do not specify stringdist.match = or numeric.match = as we did previously.\n\n## Run fastLink on the same dataset\ndedupe_output <- fastLink(\n dfA = cases_dup,\n dfB = cases_dup,\n varnames = c(\"gender\", \"first\", \"middle\", \"last\", \"yr\", \"mon\", \"day\", \"district\")\n)\n\n\n==================== \nfastLink(): Fast Probabilistic Record Linkage\n==================== \n\nIf you set return.all to FALSE, you will not be able to calculate a confusion table as a summary statistic.\ndfA and dfB are identical, assuming deduplication of a single data set.\nSetting return.all to FALSE.\n\nCalculating matches for each variable.\nGetting counts for parameter estimation.\n Parallelizing calculation using OpenMP. 1 threads out of 8 are used.\nRunning the EM algorithm.\nGetting the indices of estimated matches.\n Parallelizing calculation using OpenMP. 1 threads out of 8 are used.\nCalculating the posterior for each pair of matched observations.\nGetting the match patterns for each estimated match.\n\n\nNow, you can review the potential duplicates with getMatches(). Provide the data frame as both dfA = and dfB =, and provide the output of the fastLink() function as fl.out =. fl.out must be of class fastLink.dedupe, or in other words, the result of fastLink().\n\n## Run getMatches()\ncases_dedupe <- getMatches(\n dfA = cases_dup,\n dfB = cases_dup,\n fl.out = dedupe_output)\n\nSee the right-most column, which indicates the duplicate IDs - the final two rows are identified as being likely duplicates of rows 2 and 3.\n\n\n\n\n\n\nTo return the row numbers of rows which are likely duplicates, you can count the number of rows per unique value in the dedupe.ids column, and then filter to keep only those with more than one row. In this case this leaves rows 2 and 3.\n\ncases_dedupe %>% \n count(dedupe.ids) %>% \n filter(n > 1)\n\n dedupe.ids n\n1 2 2\n2 3 2\n\n\nTo inspect the whole rows of the likely duplicates, put the row number in this command:\n\n# displays row 2 and all likely duplicates of it\ncases_dedupe[cases_dedupe$dedupe.ids == 2,] \n\n gender first middle last yr mon day district dedupe.ids\n2 M Anthony B. Smith 1970 9 19 River 2\n10 M Tony B. Smith 1970 9 19 River 2", + "text": "14.3 Probabalistic matching\nIf you do not have a unique identifier common across datasets to join on, consider using a probabilistic matching algorithm. This would find matches between records based on similarity (e.g. Jaro–Winkler string distance, or numeric distance). Below is a simple example using the package fastLink .\nLoad packages\n\npacman::p_load(\n tidyverse, # data manipulation and visualization\n fastLink # record matching\n )\n\nHere are two small example datasets that we will use to demonstrate the probabilistic matching (cases and test_results):\nHere is the code used to make the datasets:\n\n# make datasets\n\ncases <- tribble(\n ~gender, ~first, ~middle, ~last, ~yr, ~mon, ~day, ~district,\n \"M\", \"Amir\", NA, \"Khan\", 1989, 11, 22, \"River\",\n \"M\", \"Anthony\", \"B.\", \"Smith\", 1970, 09, 19, \"River\", \n \"F\", \"Marialisa\", \"Contreras\", \"Rodrigues\", 1972, 04, 15, \"River\",\n \"F\", \"Elizabeth\", \"Casteel\", \"Chase\", 1954, 03, 03, \"City\",\n \"M\", \"Jose\", \"Sanchez\", \"Lopez\", 1996, 01, 06, \"City\",\n \"F\", \"Cassidy\", \"Jones\", \"Davis\", 1980, 07, 20, \"City\",\n \"M\", \"Michael\", \"Murphy\", \"O'Calaghan\",1969, 04, 12, \"Rural\", \n \"M\", \"Oliver\", \"Laurent\", \"De Bordow\" , 1971, 02, 04, \"River\",\n \"F\", \"Blessing\", NA, \"Adebayo\", 1955, 02, 14, \"Rural\"\n)\n\nresults <- tribble(\n ~gender, ~first, ~middle, ~last, ~yr, ~mon, ~day, ~district, ~result,\n \"M\", \"Amir\", NA, \"Khan\", 1989, 11, 22, \"River\", \"positive\",\n \"M\", \"Tony\", \"B\", \"Smith\", 1970, 09, 19, \"River\", \"positive\",\n \"F\", \"Maria\", \"Contreras\", \"Rodriguez\", 1972, 04, 15, \"Cty\", \"negative\",\n \"F\", \"Betty\", \"Castel\", \"Chase\", 1954, 03, 30, \"City\", \"positive\",\n \"F\", \"Andrea\", NA, \"Kumaraswamy\", 2001, 01, 05, \"Rural\", \"positive\", \n \"F\", \"Caroline\", NA, \"Wang\", 1988, 12, 11, \"Rural\", \"negative\",\n \"F\", \"Trang\", NA, \"Nguyen\", 1981, 06, 10, \"Rural\", \"positive\",\n \"M\", \"Olivier\" , \"Laurent\", \"De Bordeaux\", NA, NA, NA, \"River\", \"positive\",\n \"M\", \"Mike\", \"Murphy\", \"O'Callaghan\", 1969, 04, 12, \"Rural\", \"negative\",\n \"F\", \"Cassidy\", \"Jones\", \"Davis\", 1980, 07, 02, \"City\", \"positive\",\n \"M\", \"Mohammad\", NA, \"Ali\", 1942, 01, 17, \"City\", \"negative\",\n NA, \"Jose\", \"Sanchez\", \"Lopez\", 1995, 01, 06, \"City\", \"negative\",\n \"M\", \"Abubakar\", NA, \"Abullahi\", 1960, 01, 01, \"River\", \"positive\",\n \"F\", \"Maria\", \"Salinas\", \"Contreras\", 1955, 03, 03, \"River\", \"positive\"\n )\n\nThe cases dataset has 9 records of patients who are awaiting test results.\n\n\n\n\n\n\nThe test_results dataset has 14 records and contains the column result, which we want to add to the records in cases based on probabilistic matching of records.\n\n\n\n\n\n\n\nProbabilistic matching\nThe fastLink() function from the fastLink package can be used to apply a matching algorithm. Here is the basic information. You can read more detail by entering ?fastLink in your console.\n\nDefine the two data frames for comparison to arguments dfA = and dfB =.\n\nIn varnames = give all column names to be used for matching. They must all exist in both dfA and dfB.\n\nIn stringdist.match = give columns from those in varnames to be evaluated on string “distance”.\n\nIn numeric.match = give columns from those in varnames to be evaluated on numeric distance.\n\nMissing values are ignored.\n\nBy default, each row in either data frame is matched to at most one row in the other data frame. If you want to see all the evaluated matches, set dedupe.matches = FALSE. The deduplication is done using Winkler’s linear assignment solution.\n\nTip: split one date column into three separate numeric columns using day(), month(), and year() from lubridate package.\nThe default threshold for matches is 0.94 (threshold.match =) but you can adjust it higher or lower. If you define the threshold, consider that higher thresholds could yield more false-negatives (rows that do not match which actually should match) and likewise a lower threshold could yield more false-positive matches.\nBelow, the data are matched on string distance across the name and district columns, and on numeric distance for year, month, and day of birth. A match threshold of 95% probability is set.\n\nfl_output <- fastLink::fastLink(\n dfA = cases,\n dfB = results,\n varnames = c(\"gender\", \"first\", \"middle\", \"last\", \"yr\", \"mon\", \"day\", \"district\"),\n stringdist.match = c(\"first\", \"middle\", \"last\", \"district\"),\n numeric.match = c(\"yr\", \"mon\", \"day\"),\n threshold.match = 0.95)\n\n\n==================== \nfastLink(): Fast Probabilistic Record Linkage\n==================== \n\nIf you set return.all to FALSE, you will not be able to calculate a confusion table as a summary statistic.\nCalculating matches for each variable.\n\n\nGetting counts for parameter estimation.\n Parallelizing calculation using OpenMP. 1 threads out of 16 are used.\nRunning the EM algorithm.\nGetting the indices of estimated matches.\n Parallelizing calculation using OpenMP. 1 threads out of 16 are used.\nDeduping the estimated matches.\nGetting the match patterns for each estimated match.\n\n\nReview matches\nWe defined the object returned from fastLink() as fl_output. It is of class list, and it actually contains several data frames within it, detailing the results of the matching. One of these data frames is matches, which contains the most likely matches across cases and results. You can access this “matches” data frame with fl_output$matches. Below, it is saved as my_matches for ease of accessing later.\nWhen my_matches is printed, you see two column vectors: the pairs of row numbers/indices (also called “rownames”) in cases (“inds.a”) and in results (“inds.b”) representing the best matches. If a row number from a datafrane is missing, then no match was found in the other data frame at the specified match threshold.\n\n# print matches\nmy_matches <- fl_output$matches\nmy_matches\n\n inds.a inds.b\n1 1 1\n2 2 2\n3 3 3\n4 4 4\n5 8 8\n6 7 9\n7 6 10\n8 5 12\n\n\nThings to note:\n\nMatches occurred despite slight differences in name spelling and dates of birth:\n\n“Tony B. Smith” matched to “Anthony B Smith”\n\n“Maria Rodriguez” matched to “Marialisa Rodrigues”\n\n“Betty Chase” matched to “Elizabeth Chase”\n\n“Olivier Laurent De Bordeaux” matched to “Oliver Laurent De Bordow” (missing date of birth ignored)\n\n\nOne row from cases (for “Blessing Adebayo”, row 9) had no good match in results, so it is not present in my_matches.\n\nJoin based on the probabilistic matches\nTo use these matches to join results to cases, one strategy is:\n\nUse left_join() to join my_matches to cases (matching rownames in cases to “inds.a” in my_matches).\n\nThen use another left_join() to join results to cases (matching the newly-acquired “inds.b” in cases to rownames in results).\n\nBefore the joins, we should clean the three data frames:\n\nBoth dfA and dfB should have their row numbers (“rowname”) converted to a proper column.\n\nBoth the columns in my_matches are converted to class character, so they can be joined to the character rownames.\n\n\n# Clean data prior to joining\n#############################\n\n# convert cases rownames to a column \ncases_clean <- cases %>% rownames_to_column()\n\n# convert test_results rownames to a column\nresults_clean <- results %>% rownames_to_column() \n\n# convert all columns in matches dataset to character, so they can be joined to the rownames\nmatches_clean <- my_matches %>%\n mutate(across(everything(), as.character))\n\n\n\n# Join matches to dfA, then add dfB\n###################################\n# column \"inds.b\" is added to dfA\ncomplete <- left_join(cases_clean, matches_clean, by = c(\"rowname\" = \"inds.a\"))\n\n# column(s) from dfB are added \ncomplete <- left_join(complete, results_clean, by = c(\"inds.b\" = \"rowname\"))\n\nAs performed using the code above, the resulting data frame complete will contain all columns from both cases and results. Many will be appended with suffixes “.x” and “.y”, because the column names would otherwise be duplicated.\n\n\n\n\n\n\nAlternatively, to achieve only the “original” 9 records in cases with the new column(s) from results, use select() on results before the joins, so that it contains only rownames and the columns that you want to add to cases (e.g. the column result).\n\ncases_clean <- cases %>% rownames_to_column()\n\nresults_clean <- results %>%\n rownames_to_column() %>% \n select(rowname, result) # select only certain columns \n\nmatches_clean <- my_matches %>%\n mutate(across(everything(), as.character))\n\n# joins\ncomplete <- left_join(cases_clean, matches_clean, by = c(\"rowname\" = \"inds.a\"))\ncomplete <- left_join(complete, results_clean, by = c(\"inds.b\" = \"rowname\"))\n\n\n\n\n\n\n\nIf you want to subset either dataset to only the rows that matched, you can use the codes below:\n\ncases_matched <- cases[my_matches$inds.a,] # Rows in cases that matched to a row in results\nresults_matched <- results[my_matches$inds.b,] # Rows in results that matched to a row in cases\n\nOr, to see only the rows that did not match:\n\ncases_not_matched <- cases[!rownames(cases) %in% my_matches$inds.a,] # Rows in cases that did NOT match to a row in results\nresults_not_matched <- results[!rownames(results) %in% my_matches$inds.b,] # Rows in results that did NOT match to a row in cases\n\n\n\nProbabilistic deduplication\nProbabilistic matching can be used to deduplicate a dataset as well. See the page on deduplication for other methods of deduplication.\nHere we began with the cases dataset, but are now calling it cases_dup, as it has 2 additional rows that could be duplicates of previous rows: See “Tony” with “Anthony”, and “Marialisa Rodrigues” with “Maria Rodriguez”.\n\n\n\n\n\n\nRun fastLink() like before, but compare the cases_dup data frame to itself. When the two data frames provided are identical, the function assumes you want to de-duplicate. Note we do not specify stringdist.match = or numeric.match = as we did previously.\n\n## Run fastLink on the same dataset\ndedupe_output <- fastLink(\n dfA = cases_dup,\n dfB = cases_dup,\n varnames = c(\"gender\", \"first\", \"middle\", \"last\", \"yr\", \"mon\", \"day\", \"district\")\n)\n\n\n==================== \nfastLink(): Fast Probabilistic Record Linkage\n==================== \n\nIf you set return.all to FALSE, you will not be able to calculate a confusion table as a summary statistic.\ndfA and dfB are identical, assuming deduplication of a single data set.\nSetting return.all to FALSE.\n\nCalculating matches for each variable.\nGetting counts for parameter estimation.\n Parallelizing calculation using OpenMP. 1 threads out of 16 are used.\nRunning the EM algorithm.\nGetting the indices of estimated matches.\n Parallelizing calculation using OpenMP. 1 threads out of 16 are used.\nCalculating the posterior for each pair of matched observations.\nGetting the match patterns for each estimated match.\n\n\nNow, you can review the potential duplicates with getMatches(). Provide the data frame as both dfA = and dfB =, and provide the output of the fastLink() function as fl.out =. fl.out must be of class fastLink.dedupe, or in other words, the result of fastLink().\n\n## Run getMatches()\ncases_dedupe <- getMatches(\n dfA = cases_dup,\n dfB = cases_dup,\n fl.out = dedupe_output)\n\nSee the right-most column, which indicates the duplicate IDs - the final two rows are identified as being likely duplicates of rows 2 and 3.\n\n\n\n\n\n\nTo return the row numbers of rows which are likely duplicates, you can count the number of rows per unique value in the dedupe.ids column, and then filter to keep only those with more than one row. In this case this leaves rows 2 and 3.\n\ncases_dedupe %>% \n count(dedupe.ids) %>% \n filter(n > 1)\n\n dedupe.ids n\n1 2 2\n2 3 2\n\n\nTo inspect the whole rows of the likely duplicates, put the row number in this command:\n\n# displays row 2 and all likely duplicates of it\ncases_dedupe[cases_dedupe$dedupe.ids == 2,] \n\n gender first middle last yr mon day district dedupe.ids\n2 M Anthony B. Smith 1970 9 19 River 2\n10 M Tony B. Smith 1970 9 19 River 2", "crumbs": [ "Data Management", "14  Joining data" @@ -1363,7 +1363,7 @@ "href": "new_pages/joining_matching.html#resources", "title": "14  Joining data", "section": "14.5 Resources", - "text": "14.5 Resources\nThe tidyverse page on joins\nThe R for Data Science page on relational data\nTh tidyverse page on dplyr on binding\nA vignette on fastLink at the package’s Github page\nPublication describing methodology of fastLink\nPublication describing RecordLinkage package", + "text": "14.5 Resources\nThe tidyverse page on joins.\nThe R for Data Science page on relational data.\nTh tidyverse page on dplyr on binding.\nA vignette on fastLink at the package’s Github page.\nPublication describing methodology of fastLink.\nPublication describing RecordLinkage package.", "crumbs": [ "Data Management", "14  Joining data" @@ -1385,7 +1385,7 @@ "href": "new_pages/deduplication.html#preparation", "title": "15  De-duplication", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # deduplication, grouping, and slicing functions\n janitor, # function for reviewing duplicates\n stringr) # for string searches, can be used in \"rolling-up\" values\n\n\n\nImport data\nFor demonstration, we will use an example dataset that is created with the R code below.\nThe data are records of COVID-19 phone encounters, including encounters with contacts and with cases. The columns include recordID (computer-generated), personID, name, date of encounter, time of encounter, the purpose of the encounter (either to interview as a case or as a contact), and symptoms_ever (whether the person in that encounter reported ever having symptoms).\nHere is the code to create the obs dataset:\n\nobs <- data.frame(\n recordID = c(1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18),\n personID = c(1,1,2,2,3,2,4,5,6,7,2,1,3,3,4,5,5,7,8),\n name = c(\"adam\", \"adam\", \"amrish\", \"amrish\", \"mariah\", \"amrish\", \"nikhil\", \"brian\", \"smita\", \"raquel\", \"amrish\",\n \"adam\", \"mariah\", \"mariah\", \"nikhil\", \"brian\", \"brian\", \"raquel\", \"natalie\"),\n date = c(\"1/1/2020\", \"1/1/2020\", \"2/1/2020\", \"2/1/2020\", \"5/1/2020\", \"5/1/2020\", \"5/1/2020\", \"5/1/2020\", \"5/1/2020\",\"5/1/2020\", \"2/1/2020\",\n \"5/1/2020\", \"6/1/2020\", \"6/1/2020\", \"6/1/2020\", \"6/1/2020\", \"7/1/2020\", \"7/1/2020\", \"7/1/2020\"),\n time = c(\"09:00\", \"09:00\", \"14:20\", \"14:20\", \"12:00\", \"16:10\", \"13:01\", \"15:20\", \"14:20\", \"12:30\", \"10:24\",\n \"09:40\", \"07:25\", \"08:32\", \"15:36\", \"15:31\", \"07:59\", \"11:13\", \"17:12\"),\n encounter = c(1,1,1,1,1,3,1,1,1,1,2,\n 2,2,3,2,2,3,2,1),\n purpose = c(\"contact\", \"contact\", \"contact\", \"contact\", \"case\", \"case\", \"contact\", \"contact\", \"contact\", \"contact\", \"contact\",\n \"case\", \"contact\", \"contact\", \"contact\", \"contact\", \"case\", \"contact\", \"case\"),\n symptoms_ever = c(NA, NA, \"No\", \"No\", \"No\", \"Yes\", \"Yes\", \"No\", \"Yes\", NA, \"Yes\",\n \"No\", \"No\", \"No\", \"Yes\", \"Yes\", \"No\",\"No\", \"No\")) %>% \n mutate(date = as.Date(date, format = \"%d/%m/%Y\"))\n\n\nHere is the data frame\nUse the filter boxes along the top to review the encounters for each person.\n\n\n\n\n\n\nA few things to note as you review the data:\n\nThe first two records are 100% complete duplicates including duplicate recordID (must be a computer glitch!)\n\nThe second two rows are duplicates, in all columns except for recordID\n\nSeveral people had multiple phone encounters, at various dates and times, and as contacts and/or cases\n\nAt each encounter, the person was asked if they had ever had symptoms, and some of this information is missing.\n\nAnd here is a quick summary of the people and the purposes of their encounters, using tabyl() from janitor:\n\nobs %>% \n tabyl(name, purpose)\n\n name case contact\n adam 1 2\n amrish 1 3\n brian 1 2\n mariah 1 2\n natalie 1 0\n nikhil 0 2\n raquel 0 2\n smita 0 1", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # deduplication, grouping, and slicing functions\n janitor, # function for reviewing duplicates\n stringr # for string searches, can be used in \"rolling-up\" values\n ) \n\n\n\nImport data\nFor demonstration, we will use an example dataset that is created with the R code below.\nThe data are records of COVID-19 phone encounters, including encounters with contacts and with cases. The columns include recordID (computer-generated), personID, name, date of encounter, time of encounter, the purpose of the encounter (either to interview as a case or as a contact), and symptoms_ever (whether the person in that encounter reported ever having symptoms).\nHere is the code to create the obs dataset:\n\nobs <- data.frame(\n recordID = c(1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18),\n personID = c(1,1,2,2,3,2,4,5,6,7,2,1,3,3,4,5,5,7,8),\n name = c(\"adam\", \"adam\", \"amrish\", \"amrish\", \"mariah\", \"amrish\", \"nikhil\", \"brian\", \"smita\", \"raquel\", \"amrish\",\n \"adam\", \"mariah\", \"mariah\", \"nikhil\", \"brian\", \"brian\", \"raquel\", \"natalie\"),\n date = c(\"1/1/2020\", \"1/1/2020\", \"2/1/2020\", \"2/1/2020\", \"5/1/2020\", \"5/1/2020\", \"5/1/2020\", \"5/1/2020\", \"5/1/2020\",\"5/1/2020\", \"2/1/2020\",\n \"5/1/2020\", \"6/1/2020\", \"6/1/2020\", \"6/1/2020\", \"6/1/2020\", \"7/1/2020\", \"7/1/2020\", \"7/1/2020\"),\n time = c(\"09:00\", \"09:00\", \"14:20\", \"14:20\", \"12:00\", \"16:10\", \"13:01\", \"15:20\", \"14:20\", \"12:30\", \"10:24\",\n \"09:40\", \"07:25\", \"08:32\", \"15:36\", \"15:31\", \"07:59\", \"11:13\", \"17:12\"),\n encounter = c(1,1,1,1,1,3,1,1,1,1,2,\n 2,2,3,2,2,3,2,1),\n purpose = c(\"contact\", \"contact\", \"contact\", \"contact\", \"case\", \"case\", \"contact\", \"contact\", \"contact\", \"contact\", \"contact\",\n \"case\", \"contact\", \"contact\", \"contact\", \"contact\", \"case\", \"contact\", \"case\"),\n symptoms_ever = c(NA, NA, \"No\", \"No\", \"No\", \"Yes\", \"Yes\", \"No\", \"Yes\", NA, \"Yes\",\n \"No\", \"No\", \"No\", \"Yes\", \"Yes\", \"No\",\"No\", \"No\")) %>% \n mutate(date = as.Date(date, format = \"%d/%m/%Y\"))\n\n\nHere is the data frame\nUse the filter boxes along the top to review the encounters for each person.\n\n\n\n\n\n\nA few things to note as you review the data:\n\nThe first two records are 100% complete duplicates including duplicate recordID (must be a computer glitch!).\n\nThe second two rows are duplicates, in all columns except for recordID.\n\nSeveral people had multiple phone encounters, at various dates and times, and as contacts and/or cases.\nAt each encounter, the person was asked if they had ever had symptoms, and some of this information is missing.\n\nAnd here is a quick summary of the people and the purposes of their encounters, using tabyl() from janitor:\n\nobs %>% \n tabyl(name, purpose)\n\n name case contact\n adam 1 2\n amrish 1 3\n brian 1 2\n mariah 1 2\n natalie 1 0\n nikhil 0 2\n raquel 0 2\n smita 0 1", "crumbs": [ "Data Management", "15  De-duplication" @@ -1396,7 +1396,7 @@ "href": "new_pages/deduplication.html#deduplication", "title": "15  De-duplication", "section": "15.2 Deduplication", - "text": "15.2 Deduplication\nThis section describes how to review and remove duplicate rows in a data frame. It also show how to handle duplicate elements in a vector.\n\n\nExamine duplicate rows\nTo quickly review rows that have duplicates, you can use get_dupes() from the janitor package. By default, all columns are considered when duplicates are evaluated - rows returned by the function are 100% duplicates considering the values in all columns.\nIn the obs data frame, the first two rows are 100% duplicates - they have the same value in every column (including the recordID column, which is supposed to be unique - it must be some computer glitch). The returned data frame automatically includes a new column dupe_count on the right side, showing the number of rows with that combination of duplicate values.\n\n# 100% duplicates across all columns\nobs %>% \n janitor::get_dupes()\n\n\n\n\n\n\n\nSee the original data\nHowever, if we choose to ignore recordID, the 3rd and 4th rows rows are also duplicates of each other. That is, they have the same values in all columns except for recordID. You can specify specific columns to be ignored in the function using a - minus symbol.\n\n# Duplicates when column recordID is not considered\nobs %>% \n janitor::get_dupes(-recordID) # if multiple columns, wrap them in c()\n\n\n\n\n\n\n\nYou can also positively specify the columns to consider. Below, only rows that have the same values in the name and purpose columns are returned. Notice how “amrish” now has dupe_count equal to 3 to reflect his three “contact” encounters.\n*Scroll left for more rows**\n\n# duplicates based on name and purpose columns ONLY\nobs %>% \n janitor::get_dupes(name, purpose)\n\n\n\n\n\n\n\nSee the original data.\nSee ?get_dupes for more details, or see this online reference\n\n\n\nKeep only unique rows\nTo keep only unique rows of a data frame, use distinct() from dplyr (as demonstrated in the Cleaning data and core functions page). Rows that are duplicates are removed such that only the first of such rows is kept. By default, “first” means the highest rownumber (order of rows top-to-bottom). Only unique rows remain.\nIn the example below, we run distinct() such that the column recordID is excluded from consideration - thus two duplicate rows are removed. The first row (for “adam”) was 100% duplicated and has been removed. Also row 3 (for “amrish”) was a duplicate in every column except recordID (which is not being considered) and so is also removed. The obs dataset n is now nrow(obs)-2, not nrow(obs) rows).\nScroll to the left to see the entire data frame\n\n# added to a chain of pipes (e.g. data cleaning)\nobs %>% \n distinct(across(-recordID), # reduces data frame to only unique rows (keeps first one of any duplicates)\n .keep_all = TRUE) \n\n# if outside pipes, include the data as first argument \n# distinct(obs)\n\n\n\n\n\n\n\nCAUTION: If using distinct() on grouped data, the function will apply to each group.\nDeduplicate based on specific columns\nYou can also specify columns to be the basis for de-duplication. In this way, the de-duplication only applies to rows that are duplicates within the specified columns. Unless you set .keep_all = TRUE, all columns not mentioned will be dropped.\nIn the example below, the de-duplication only applies to rows that have identical values for name and purpose columns. Thus, “brian” has only 2 rows instead of 3 - his first “contact” encounter and his only “case” encounter. To adjust so that brian’s latest encounter of each purpose is kept, see the tab on Slicing within groups.\nScroll to the left to see the entire data frame\n\n# added to a chain of pipes (e.g. data cleaning)\nobs %>% \n distinct(name, purpose, .keep_all = TRUE) %>% # keep rows unique by name and purpose, retain all columns\n arrange(name) # arrange for easier viewing\n\n\n\n\n\n\n\nSee the original data.\n\n\n\nDeduplicate elements in a vector\nThe function duplicated() from base R will evaluate a vector (column) and return a logical vector of the same length (TRUE/FALSE). The first time a value appears, it will return FALSE (not a duplicate), and subsequent times that value appears it will return TRUE. Note how NA is treated the same as any other value.\n\nx <- c(1, 1, 2, NA, NA, 4, 5, 4, 4, 1, 2)\nduplicated(x)\n\n [1] FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE TRUE TRUE TRUE\n\n\nTo return only the duplicated elements, you can use brackets to subset the original vector:\n\nx[duplicated(x)]\n\n[1] 1 NA 4 4 1 2\n\n\nTo return only the unique elements, use unique() from base R. To remove NAs from the output, nest na.omit() within unique().\n\nunique(x) # alternatively, use x[!duplicated(x)]\n\n[1] 1 2 NA 4 5\n\nunique(na.omit(x)) # remove NAs \n\n[1] 1 2 4 5\n\n\n\n\n\nUsing base R\nTo return duplicate rows\nIn base R, you can also see which rows are 100% duplicates in a data frame df with the command duplicated(df) (returns a logical vector of the rows).\nThus, you can also use the base subset [ ] on the data frame to see the duplicated rows with df[duplicated(df),] (don’t forget the comma, meaning that you want to see all columns!).\nTo return unique rows\nSee the notes above. To see the unique rows you add the logical negator ! in front of the duplicated() function:\ndf[!duplicated(df),]\nTo return rows that are duplicates of only certain columns\nSubset the df that is within the duplicated() parentheses, so this function will operate on only certain columns of the df.\nTo specify the columns, provide column numbers or names after a comma (remember, all this is within the duplicated() function).\nBe sure to keep the comma , outside after the duplicated() function as well!\nFor example, to evaluate only columns 2 through 5 for duplicates: df[!duplicated(df[, 2:5]),]\nTo evaluate only columns name and purpose for duplicates: df[!duplicated(df[, c(\"name\", \"purpose)]),]", + "text": "15.2 Deduplication\nThis section describes how to review and remove duplicate rows in a data frame. It also show how to handle duplicate elements in a vector.\n\n\nExamine duplicate rows\nTo quickly review rows that have duplicates, you can use get_dupes() from the janitor package. By default, all columns are considered when duplicates are evaluated - rows returned by the function are 100% duplicates considering the values in all columns.\nIn the obs data frame, the first two rows are 100% duplicates - they have the same value in every column (including the recordID column, which is supposed to be unique). The returned data frame automatically includes a new column dupe_count on the right side, showing the number of rows with that combination of duplicate values.\n\n# 100% duplicates across all columns\nobs %>% \n janitor::get_dupes()\n\n\n\n\n\n\n\nSee the original data\nHowever, if we choose to ignore recordID, the 3rd and 4th rows rows are also duplicates of each other. That is, they have the same values in all columns except for recordID. You can specify specific columns to be ignored in the function using a - minus symbol.\n\n# Duplicates when column recordID is not considered\nobs %>% \n janitor::get_dupes(-recordID) # if multiple columns, wrap them in c()\n\n\n\n\n\n\n\nYou can also positively specify the columns to consider. Below, only rows that have the same values in the name and purpose columns are returned. Notice how “amrish” now has dupe_count equal to 3 to reflect his three “contact” encounters.\nScroll left for more rows\n\n# duplicates based on name and purpose columns ONLY\nobs %>% \n janitor::get_dupes(name, purpose)\n\n\n\n\n\n\n\nSee the original data.\nSee ?get_dupes for more details, or see this online reference\n\n\n\nKeep only unique rows\nTo keep only unique rows of a data frame, use distinct() from dplyr (as demonstrated in the Cleaning data and core functions page). Rows that are duplicates are removed such that only the first of such rows is kept. By default, “first” means the highest rownumber (order of rows top-to-bottom). Only unique rows remain.\nIn the example below, we run distinct() such that the column recordID is excluded from consideration - thus two duplicate rows are removed. The first row (for “adam”) was 100% duplicated and has been removed. Also row 3 (for “amrish”) was a duplicate in every column except recordID (which is not being considered) and so is also removed. The obs dataset n is now nrow(obs)-2, not nrow(obs) rows).\nScroll to the left to see the entire data frame\n\n# added to a chain of pipes (e.g. data cleaning)\nobs %>% \n distinct(across(-recordID), # reduces data frame to only unique rows (keeps first one of any duplicates)\n .keep_all = TRUE) \n\n# if outside pipes, include the data as first argument \n# distinct(obs)\n\n\n\n\n\n\n\nCAUTION: If using distinct() on grouped data, the function will apply to each group.\nDeduplicate based on specific columns\nYou can also specify columns to be the basis for de-duplication. In this way, the de-duplication only applies to rows that are duplicates within the specified columns. Unless you set .keep_all = TRUE, all columns not mentioned will be dropped.\nIn the example below, the de-duplication only applies to rows that have identical values for name and purpose columns. Thus, “brian” has only 2 rows instead of 3 - his first “contact” encounter and his only “case” encounter. To adjust so that brian’s latest encounter of each purpose is kept, see the tab on Slicing within groups.\nScroll to the left to see the entire data frame\n\n# added to a chain of pipes (e.g. data cleaning)\nobs %>% \n distinct(name, purpose, .keep_all = TRUE) %>% # keep rows unique by name and purpose, retain all columns\n arrange(name) # arrange for easier viewing\n\n\n\n\n\n\n\nSee the original data.\n\n\n\nDeduplicate elements in a vector\nThe function duplicated() from base R will evaluate a vector (column) and return a logical vector of the same length (TRUE/FALSE). The first time a value appears, it will return FALSE (not a duplicate), and subsequent times that value appears it will return TRUE. Note how NA is treated the same as any other value.\n\nx <- c(1, 1, 2, NA, NA, 4, 5, 4, 4, 1, 2)\nduplicated(x)\n\n [1] FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE TRUE TRUE TRUE\n\n\nTo return only the duplicated elements, you can use brackets to subset the original vector:\n\nx[duplicated(x)]\n\n[1] 1 NA 4 4 1 2\n\n\nTo return only the unique elements, use unique() from base R. To remove NAs from the output, nest na.omit() within unique().\n\nunique(x) # alternatively, use x[!duplicated(x)]\n\n[1] 1 2 NA 4 5\n\nunique(na.omit(x)) # remove NAs \n\n[1] 1 2 4 5\n\n\n\n\n\nUsing base R\nTo return duplicate rows\nIn base R, you can also see which rows are 100% duplicates in a data frame df with the command duplicated(df) (returns a logical vector of the rows).\nThus, you can also use the base subset [ ] on the data frame to see the duplicated rows with df[duplicated(df),] (don’t forget the comma, meaning that you want to see all columns!).\nTo return unique rows\nSee the notes above. To see the unique rows you add the logical negator ! in front of the duplicated() function:\ndf[!duplicated(df),]\nTo return rows that are duplicates of only certain columns\nSubset the df that is within the duplicated() parentheses, so this function will operate on only certain columns of the df.\nTo specify the columns, provide column numbers or names after a comma (remember, all this is within the duplicated() function).\nBe sure to keep the comma , outside after the duplicated() function as well!\nFor example, to evaluate only columns 2 through 5 for duplicates: df[!duplicated(df[, 2:5]),]\nTo evaluate only columns name and purpose for duplicates: df[!duplicated(df[, c(\"name\", \"purpose)]),]", "crumbs": [ "Data Management", "15  De-duplication" @@ -1407,7 +1407,7 @@ "href": "new_pages/deduplication.html#slicing", "title": "15  De-duplication", "section": "15.3 Slicing", - "text": "15.3 Slicing\nTo “slice” a data frame to apply a filter on the rows by row number/position. This becomes particularly useful if you have multiple rows per functional group (e.g. per “person”) and you only want to keep one or some of them.\nThe basic slice() function accepts numbers and returns rows in those positions. If the numbers provided are positive, only they are returned. If negative, those rows are not returned. Numbers must be either all positive or all negative.\n\nobs %>% slice(4) # return the 4th row\n\n recordID personID name date time encounter purpose symptoms_ever\n1 3 2 amrish 2020-01-02 14:20 1 contact No\n\n\n\nobs %>% slice(c(2,4)) # return rows 2 and 4\n\n recordID personID name date time encounter purpose symptoms_ever\n1 1 1 adam 2020-01-01 09:00 1 contact <NA>\n2 3 2 amrish 2020-01-02 14:20 1 contact No\n\n#obs %>% slice(c(2:4)) # return rows 2 through 4\n\nSee the original data.\nThere are several variations: These should be provided with a column and a number of rows to return (to n =).\n\nslice_min() and slice_max() keep only the row(s) with the minimium or maximum value(s) of the specified column. This also works to return the “min” and “max” of ordered factors.\n\nslice_head() and slice_tail() - keep only the first or last row(s).\n\nslice_sample() - keep only a random sample of the rows.\n\n\nobs %>% slice_max(encounter, n = 1) # return rows with the largest encounter number\n\n recordID personID name date time encounter purpose symptoms_ever\n1 5 2 amrish 2020-01-05 16:10 3 case Yes\n2 13 3 mariah 2020-01-06 08:32 3 contact No\n3 16 5 brian 2020-01-07 07:59 3 case No\n\n\nUse arguments n = or prop = to specify the number or proportion of rows to keep. If not using the function in a pipe chain, provide the data argument first (e.g. slice(data, n = 2)). See ?slice for more information.\nOther arguments:\n.order_by = used in slice_min() and slice_max() this is a column to order by before slicing.\nwith_ties = TRUE by default, meaning ties are kept.\n.preserve = FALSE by default. If TRUE then the grouping structure is re-calculated after slicing.\nweight_by = Optional, numeric column to weight by (bigger number more likely to get sampled). Also replace = for whether sampling is done with/without replacement.\nTIP: When using slice_max() and slice_min(), be sure to specify/write the n = (e.g. n = 2, not just 2). Otherwise you may get an error Error:…is not empty. \nNOTE: You may encounter the function top_n(), which has been superseded by the slice functions.\n\n\nSlice with groups\nThe slice_*() functions can be very useful if applied to a grouped data frame because the slice operation is performed on each group separately. Use the function group_by() in conjunction with slice() to group the data to take a slice from each group.\nThis is helpful for de-duplication if you have multiple rows per person but only want to keep one of them. You first use group_by() with key columns that are the same per person, and then use a slice function on a column that will differ among the grouped rows.\nIn the example below, to keep only the latest encounter per person, we group the rows by name and then use slice_max() with n = 1 on the date column. Be aware! To apply a function like slice_max() on dates, the date column must be class Date.\nBy default, “ties” (e.g. same date in this scenario) are kept, and we would still get multiple rows for some people (e.g. adam). To avoid this we set with_ties = FALSE. We get back only one row per person.\nCAUTION: If using arrange(), specify .by_group = TRUE to have the data arranged within each group.\nDANGER: If with_ties = FALSE, the first row of a tie is kept. This may be deceptive. See how for Mariah, she has two encounters on her latest date (6 Jan) and the first (earliest) one was kept. Likely, we want to keep her later encounter on that day. See how to “break” these ties in the next example. \n\nobs %>% \n group_by(name) %>% # group the rows by 'name'\n slice_max(date, # keep row per group with maximum date value \n n = 1, # keep only the single highest row \n with_ties = F) # if there's a tie (of date), take the first row\n\n\n\n\n\n\n\nAbove, for example we can see that only Amrish’s row on 5 Jan was kept, and only Brian’s row on 7 Jan was kept. See the original data.\nBreaking “ties”\nMultiple slice statements can be run to “break ties”. In this case, if a person has multiple encounters on their latest date, the encounter with the latest time is kept (lubridate::hm() is used to convert the character times to a sortable time class).\nNote how now, the one row kept for “Mariah” on 6 Jan is encounter 3 from 08:32, not encounter 2 at 07:25.\n\n# Example of multiple slice statements to \"break ties\"\nobs %>%\n group_by(name) %>%\n \n # FIRST - slice by latest date\n slice_max(date, n = 1, with_ties = TRUE) %>% \n \n # SECOND - if there is a tie, select row with latest time; ties prohibited\n slice_max(lubridate::hm(time), n = 1, with_ties = FALSE)\n\n\n\n\n\n\n\nIn the example above, it would also have been possible to slice by encounter number, but we showed the slice on date and time for example purposes.\nTIP: To use slice_max() or slice_min() on a “character” column, mutate it to an ordered factor class!\nSee the original data.\n\n\n\nKeep all but mark them\nIf you want to keep all records but mark only some for analysis, consider a two-step approach utilizing a unique recordID/encounter number:\n\nReduce/slice the orginal data frame to only the rows for analysis. Save/retain this reduced data frame.\n\nIn the original data frame, mark rows as appropriate with case_when(), based on whether their record unique identifier (recordID in this example) is present in the reduced data frame.\n\n\n# 1. Define data frame of rows to keep for analysis\nobs_keep <- obs %>%\n group_by(name) %>%\n slice_max(encounter, n = 1, with_ties = FALSE) # keep only latest encounter per person\n\n\n# 2. Mark original data frame\nobs_marked <- obs %>%\n\n # make new dup_record column\n mutate(dup_record = case_when(\n \n # if record is in obs_keep data frame\n recordID %in% obs_keep$recordID ~ \"For analysis\", \n \n # all else marked as \"Ignore\" for analysis purposes\n TRUE ~ \"Ignore\"))\n\n# print\nobs_marked\n\n recordID personID name date time encounter purpose symptoms_ever\n1 1 1 adam 2020-01-01 09:00 1 contact <NA>\n2 1 1 adam 2020-01-01 09:00 1 contact <NA>\n3 2 2 amrish 2020-01-02 14:20 1 contact No\n4 3 2 amrish 2020-01-02 14:20 1 contact No\n5 4 3 mariah 2020-01-05 12:00 1 case No\n6 5 2 amrish 2020-01-05 16:10 3 case Yes\n7 6 4 nikhil 2020-01-05 13:01 1 contact Yes\n8 7 5 brian 2020-01-05 15:20 1 contact No\n9 8 6 smita 2020-01-05 14:20 1 contact Yes\n10 9 7 raquel 2020-01-05 12:30 1 contact <NA>\n11 10 2 amrish 2020-01-02 10:24 2 contact Yes\n12 11 1 adam 2020-01-05 09:40 2 case No\n13 12 3 mariah 2020-01-06 07:25 2 contact No\n14 13 3 mariah 2020-01-06 08:32 3 contact No\n15 14 4 nikhil 2020-01-06 15:36 2 contact Yes\n16 15 5 brian 2020-01-06 15:31 2 contact Yes\n17 16 5 brian 2020-01-07 07:59 3 case No\n18 17 7 raquel 2020-01-07 11:13 2 contact No\n19 18 8 natalie 2020-01-07 17:12 1 case No\n dup_record\n1 Ignore\n2 Ignore\n3 Ignore\n4 Ignore\n5 Ignore\n6 For analysis\n7 Ignore\n8 Ignore\n9 For analysis\n10 Ignore\n11 Ignore\n12 For analysis\n13 Ignore\n14 For analysis\n15 For analysis\n16 Ignore\n17 For analysis\n18 For analysis\n19 For analysis\n\n\n\n\n\n\n\n\nSee the original data.\n\n\n\nCalculate row completeness\nCreate a column that contains a metric for the row’s completeness (non-missingness). This could be helpful when deciding which rows to prioritize over others when de-duplicating/slicing.\nIn this example, “key” columns over which you want to measure completeness are saved in a vector of column names.\nThen the new column key_completeness is created with mutate(). The new value in each row is defined as a calculated fraction: the number of non-missing values in that row among the key columns, divided by the number of key columns.\nThis involves the function rowSums() from base R. Also used is ., which within piping refers to the data frame at that point in the pipe (in this case, it is being subset with brackets []).\n*Scroll to the right to see more rows**\n\n# create a \"key variable completeness\" column\n# this is a *proportion* of the columns designated as \"key_cols\" that have non-missing values\n\nkey_cols = c(\"personID\", \"name\", \"symptoms_ever\")\n\nobs %>% \n mutate(key_completeness = rowSums(!is.na(.[,key_cols]))/length(key_cols)) \n\n\n\n\n\n\n\nSee the original data.", + "text": "15.3 Slicing\nTo “slice” a data frame to apply a filter on the rows by row number/position. This becomes particularly useful if you have multiple rows per functional group (e.g. per “person”) and you only want to keep one or some of them.\nThe basic slice() function accepts numbers and returns rows in those positions. If the numbers provided are positive, only they are returned. If negative, those rows are not returned. Numbers must be either all positive or all negative.\n\nobs %>% \n slice(4) # return the 4th row\n\n recordID personID name date time encounter purpose symptoms_ever\n1 3 2 amrish 2020-01-02 14:20 1 contact No\n\n\n\nobs %>% \n slice(c(2,4)) # return rows 2 and 4\n\n recordID personID name date time encounter purpose symptoms_ever\n1 1 1 adam 2020-01-01 09:00 1 contact <NA>\n2 3 2 amrish 2020-01-02 14:20 1 contact No\n\n\nSee the original data.\nThere are several variations: These should be provided with a column and a number of rows to return (to n =).\n\nslice_min() and slice_max() keep only the row(s) with the minimium or maximum value(s) of the specified column. This also works to return the “min” and “max” of ordered factors.\n\nslice_head() and slice_tail() - keep only the first or last row(s).\n\nslice_sample() - keep only a random sample of the rows.\n\n\nobs %>% \n slice_max(encounter, n = 1) # return rows with the largest encounter number\n\n recordID personID name date time encounter purpose symptoms_ever\n1 5 2 amrish 2020-01-05 16:10 3 case Yes\n2 13 3 mariah 2020-01-06 08:32 3 contact No\n3 16 5 brian 2020-01-07 07:59 3 case No\n\n\nUse arguments n = or prop = to specify the number or proportion of rows to keep. If not using the function in a pipe chain, provide the data argument first (e.g. slice(data, n = 2)). See ?slice for more information.\nOther arguments:\n\n.order_by = used in slice_min() and slice_max() this is a column to order by before slicing.\n\nwith_ties = TRUE by default, meaning ties are kept.\n\n.preserve = FALSE by default. If TRUE then the grouping structure is re-calculated after slicing.\n\nweight_by = Optional, numeric column to weight by (bigger number more likely to get sampled). Also * replace = for whether sampling is done with/without replacement.\n\nTIP: When using slice_max() and slice_min(), be sure to specify/write the n = (e.g. n = 2, not just 2). Otherwise you may get an error Error:…is not empty. \nNOTE: You may encounter the function top_n(), which has been superseded by the slice functions.\n\n\nSlice with groups\nThe slice_*() functions can be very useful if applied to a grouped data frame because the slice operation is performed on each group separately. Use the function group_by() in conjunction with slice() to group the data to take a slice from each group.\nThis is helpful for de-duplication if you have multiple rows per person but only want to keep one of them. You first use group_by() with key columns that are the same per person, and then use a slice function on a column that will differ among the grouped rows.\nIn the example below, to keep only the latest encounter per person, we group the rows by name and then use slice_max() with n = 1 on the date column.\nBe aware! To apply a function like slice_max() on dates, the date column must be class Date.\nBy default, “ties” (e.g. same date in this scenario) are kept, and we would still get multiple rows for some people (e.g. adam). To avoid this we set with_ties = FALSE. We get back only one row per person.\nCAUTION: If using arrange(), specify .by_group = TRUE to have the data arranged within each group.\nDANGER: If with_ties = FALSE, the first row of a tie is kept. This may be deceptive. See how for Mariah, she has two encounters on her latest date (6 Jan) and the first (earliest) one was kept. Likely, we want to keep her later encounter on that day. See how to “break” these ties in the next example. \n\nobs %>% \n group_by(name) %>% # group the rows by 'name'\n slice_max(date, # keep row per group with maximum date value \n n = 1, # keep only the single highest row \n with_ties = F) # if there's a tie (of date), take the first row\n\n\n\n\n\n\n\nAbove, for example we can see that only Amrish’s row on 5 Jan was kept, and only Brian’s row on 7 Jan was kept. See the original data.\nBreaking “ties”\nMultiple slice statements can be run to “break ties”. In this case, if a person has multiple encounters on their latest date, the encounter with the latest time is kept (lubridate::hm() is used to convert the character times to a sortable time class).\nNote how now, the one row kept for “Mariah” on 6 Jan is encounter 3 from 08:32, not encounter 2 at 07:25.\n\n# Example of multiple slice statements to \"break ties\"\nobs %>%\n group_by(name) %>%\n \n # FIRST - slice by latest date\n slice_max(date, n = 1, with_ties = TRUE) %>% \n \n # SECOND - if there is a tie, select row with latest time; ties prohibited\n slice_max(lubridate::hm(time), n = 1, with_ties = FALSE)\n\n\n\n\n\n\n\nIn the example above, it would also have been possible to slice by encounter number, but we showed the slice on date and time for example purposes.\nTIP: To use slice_max() or slice_min() on a “character” column, mutate it to an ordered factor class!\nSee the original data.\n\n\n\nKeep all but mark them\nIf you want to keep all records but mark only some for analysis, consider a two-step approach utilizing a unique recordID/encounter number:\n\nReduce/slice the orginal data frame to only the rows for analysis. Save/retain this reduced data frame.\n\nIn the original data frame, mark rows as appropriate with case_when(), based on whether their record unique identifier (recordID in this example) is present in the reduced data frame.\n\n\n# 1. Define data frame of rows to keep for analysis\nobs_keep <- obs %>%\n group_by(name) %>%\n slice_max(encounter, \n n = 1, \n with_ties = FALSE) # keep only latest encounter per person\n\n\n# 2. Mark original data frame\nobs_marked <- obs %>%\n\n # make new dup_record column\n mutate(dup_record = case_when(\n \n # if record is in obs_keep data frame\n recordID %in% obs_keep$recordID ~ \"For analysis\", \n \n # all else marked as \"Ignore\" for analysis purposes\n TRUE ~ \"Ignore\"))\n\n# print\nobs_marked\n\n recordID personID name date time encounter purpose symptoms_ever\n1 1 1 adam 2020-01-01 09:00 1 contact <NA>\n2 1 1 adam 2020-01-01 09:00 1 contact <NA>\n3 2 2 amrish 2020-01-02 14:20 1 contact No\n4 3 2 amrish 2020-01-02 14:20 1 contact No\n5 4 3 mariah 2020-01-05 12:00 1 case No\n6 5 2 amrish 2020-01-05 16:10 3 case Yes\n7 6 4 nikhil 2020-01-05 13:01 1 contact Yes\n8 7 5 brian 2020-01-05 15:20 1 contact No\n9 8 6 smita 2020-01-05 14:20 1 contact Yes\n10 9 7 raquel 2020-01-05 12:30 1 contact <NA>\n11 10 2 amrish 2020-01-02 10:24 2 contact Yes\n12 11 1 adam 2020-01-05 09:40 2 case No\n13 12 3 mariah 2020-01-06 07:25 2 contact No\n14 13 3 mariah 2020-01-06 08:32 3 contact No\n15 14 4 nikhil 2020-01-06 15:36 2 contact Yes\n16 15 5 brian 2020-01-06 15:31 2 contact Yes\n17 16 5 brian 2020-01-07 07:59 3 case No\n18 17 7 raquel 2020-01-07 11:13 2 contact No\n19 18 8 natalie 2020-01-07 17:12 1 case No\n dup_record\n1 Ignore\n2 Ignore\n3 Ignore\n4 Ignore\n5 Ignore\n6 For analysis\n7 Ignore\n8 Ignore\n9 For analysis\n10 Ignore\n11 Ignore\n12 For analysis\n13 Ignore\n14 For analysis\n15 For analysis\n16 Ignore\n17 For analysis\n18 For analysis\n19 For analysis\n\n\n\n\n\n\n\n\nSee the original data.\n\n\n\nCalculate row completeness\nCreate a column that contains a metric for the row’s completeness (non-missingness). This could be helpful when deciding which rows to prioritize over others when de-duplicating/slicing.\nIn this example, “key” columns over which you want to measure completeness are saved in a vector of column names.\nThen the new column key_completeness is created with mutate(). The new value in each row is defined as a calculated fraction: the number of non-missing values in that row among the key columns, divided by the number of key columns.\nThis involves the function rowSums() from base R. Also used is ., which within piping refers to the data frame at that point in the pipe (in this case, it is being subset with brackets []).\nScroll to the right to see more rows\n\n# create a \"key variable completeness\" column\n# this is a *proportion* of the columns designated as \"key_cols\" that have non-missing values\n\nkey_cols = c(\"personID\", \"name\", \"symptoms_ever\")\n\nobs %>% \n mutate(key_completeness = rowSums(!is.na(.[,key_cols]))/length(key_cols)) \n\n\n\n\n\n\n\nSee the original data.", "crumbs": [ "Data Management", "15  De-duplication" @@ -1418,7 +1418,7 @@ "href": "new_pages/deduplication.html#str_rollup", "title": "15  De-duplication", "section": "15.4 Roll-up values", - "text": "15.4 Roll-up values\nThis section describes:\n\nHow to “roll-up” values from multiple rows into just one row, with some variations\n\nOnce you have “rolled-up” values, how to overwrite/prioritize the values in each cell\n\nThis tab uses the example dataset from the Preparation tab.\n\n\nRoll-up values into one row\nThe code example below uses group_by() and summarise() to group rows by person, and then paste together all unique values within the grouped rows. Thus, you get one summary row per person. A few notes:\n\nA suffix is appended to all new columns (“_roll” in this example)\n\nIf you want to show only unique values per cell, then wrap the na.omit() with unique()\n\nna.omit() removes NA values, but if this is not desired it can be removed paste0(.x)…\n\n\n# \"Roll-up\" values into one row per group (per \"personID\") \ncases_rolled <- obs %>% \n \n # create groups by name\n group_by(personID) %>% \n \n # order the rows within each group (e.g. by date)\n arrange(date, .by_group = TRUE) %>% \n \n # For each column, paste together all values within the grouped rows, separated by \";\"\n summarise(\n across(everything(), # apply to all columns\n ~paste0(na.omit(.x), collapse = \"; \"))) # function is defined which combines non-NA values\n\nThe result is one row per group (ID), with entries arranged by date and pasted together. Scroll to the left to see more rows\n\n\n\n\n\n\nSee the original data.\nThis variation shows unique values only:\n\n# Variation - show unique values only \ncases_rolled <- obs %>% \n group_by(personID) %>% \n arrange(date, .by_group = TRUE) %>% \n summarise(\n across(everything(), # apply to all columns\n ~paste0(unique(na.omit(.x)), collapse = \"; \"))) # function is defined which combines unique non-NA values\n\n\n\n\n\n\n\nThis variation appends a suffix to each column.\nIn this case “_roll” to signify that it has been rolled:\n\n# Variation - suffix added to column names \ncases_rolled <- obs %>% \n group_by(personID) %>% \n arrange(date, .by_group = TRUE) %>% \n summarise(\n across(everything(), \n list(roll = ~paste0(na.omit(.x), collapse = \"; \")))) # _roll is appended to column names\n\n\n\n\n\n\n\n\n\n\nOverwrite values/hierarchy\nIf you then want to evaluate all of the rolled values, and keep only a specific value (e.g. “best” or “maximum” value), you can use mutate() across the desired columns, to implement case_when(), which uses str_detect() from the stringr package to sequentially look for string patterns and overwrite the cell content.\n\n# CLEAN CASES\n#############\ncases_clean <- cases_rolled %>% \n \n # clean Yes-No-Unknown vars: replace text with \"highest\" value present in the string\n mutate(across(c(contains(\"symptoms_ever\")), # operates on specified columns (Y/N/U)\n list(mod = ~case_when( # adds suffix \"_mod\" to new cols; implements case_when()\n \n str_detect(.x, \"Yes\") ~ \"Yes\", # if \"Yes\" is detected, then cell value converts to yes\n str_detect(.x, \"No\") ~ \"No\", # then, if \"No\" is detected, then cell value converts to no\n str_detect(.x, \"Unknown\") ~ \"Unknown\", # then, if \"Unknown\" is detected, then cell value converts to Unknown\n TRUE ~ as.character(.x)))), # then, if anything else if it kept as is\n .keep = \"unused\") # old columns removed, leaving only _mod columns\n\nNow you can see in the column symptoms_ever that if the person EVER said “Yes” to symptoms, then only “Yes” is displayed.\n\n\n\n\n\n\nSee the original data.", + "text": "15.4 Roll-up values\nThis section describes:\n\nHow to “roll-up” values from multiple rows into just one row, with some variations.\n\nOnce you have “rolled-up” values, how to overwrite/prioritize the values in each cell.\n\nThis tab uses the example dataset from the Preparation tab.\n\n\nRoll-up values into one row\nThe code example below uses group_by() and summarise() to group rows by person, and then paste together all unique values within the grouped rows. Thus, you get one summary row per person. A few notes:\n\nA suffix is appended to all new columns (“_roll” in this example).\n\nIf you want to show only unique values per cell, then wrap the na.omit() with unique().\n\nna.omit() removes NA values, but if this is not desired it can be removed paste0(.x).\n\n\n# \"Roll-up\" values into one row per group (per \"personID\") \ncases_rolled <- obs %>% \n \n # create groups by name\n group_by(personID) %>% \n \n # order the rows within each group (e.g. by date)\n arrange(date, .by_group = TRUE) %>% \n \n # For each column, paste together all values within the grouped rows, separated by \";\"\n summarise(\n across(everything(), # apply to all columns\n ~paste0(na.omit(.x), collapse = \"; \"))) # function is defined which combines non-NA values\n\nThe result is one row per group (ID), with entries arranged by date and pasted together. Scroll to the left to see more rows\n\n\n\n\n\n\nSee the original data.\nThis variation shows unique values only:\n\n# Variation - show unique values only \ncases_rolled <- obs %>% \n group_by(personID) %>% \n arrange(date, .by_group = TRUE) %>% \n summarise(\n across(everything(), # apply to all columns\n ~paste0(unique(na.omit(.x)), collapse = \"; \"))) # function is defined which combines unique non-NA values\n\n\n\n\n\n\n\nThis variation appends a suffix to each column.\nIn this case “_roll” to signify that it has been rolled:\n\n# Variation - suffix added to column names \ncases_rolled <- obs %>% \n group_by(personID) %>% \n arrange(date, .by_group = TRUE) %>% \n summarise(\n across(everything(), \n list(roll = ~paste0(na.omit(.x), collapse = \"; \")))) # _roll is appended to column names\n\n\n\n\n\n\n\n\n\n\nOverwrite values/hierarchy\nIf you then want to evaluate all of the rolled values, and keep only a specific value (e.g. “best” or “maximum” value), you can use mutate() across the desired columns, to implement case_when(), which uses str_detect() from the stringr package to sequentially look for string patterns and overwrite the cell content.\n\n# CLEAN CASES\n#############\ncases_clean <- cases_rolled %>% \n \n # clean Yes-No-Unknown vars: replace text with \"highest\" value present in the string\n mutate(across(c(contains(\"symptoms_ever\")), # operates on specified columns (Y/N/U)\n list(mod = ~case_when( # adds suffix \"_mod\" to new cols; implements case_when()\n \n str_detect(.x, \"Yes\") ~ \"Yes\", # if \"Yes\" is detected, then cell value converts to yes\n str_detect(.x, \"No\") ~ \"No\", # then, if \"No\" is detected, then cell value converts to no\n str_detect(.x, \"Unknown\") ~ \"Unknown\", # then, if \"Unknown\" is detected, then cell value converts to Unknown\n TRUE ~ as.character(.x)))), # then, if anything else if it kept as is\n .keep = \"unused\") # old columns removed, leaving only _mod columns\n\nNow you can see in the column symptoms_ever that if the person EVER said “Yes” to symptoms, then only “Yes” is displayed.\n\n\n\n\n\n\nSee the original data.", "crumbs": [ "Data Management", "15  De-duplication" @@ -1473,7 +1473,7 @@ "href": "new_pages/iteration.html#for-loops", "title": "16  Iteration, loops, and lists", "section": "16.2 for loops", - "text": "16.2 for loops\n\nfor loops in R\nfor loops are not emphasized in R, but are common in other programming languages. As a beginner, they can be helpful to learn and practice with because they are easier to “explore”, “de-bug”, and otherwise grasp exactly what is happening for each iteration, especially when you are not yet comfortable writing your own functions.\nYou may move quickly through for loops to iterating with mapped functions with purrr (see section below).\n\n\nCore components\nA for loop has three core parts:\n\nThe sequence of items to iterate through\n\nThe operations to conduct per item in the sequence\n\nThe container for the results (optional)\n\nThe basic syntax is: for (item in sequence) {do operations using item}. Note the parentheses and the curly brackets. The results could be printed to console, or stored in a container R object.\nA simple for loop example is below.\n\nfor (num in c(1,2,3,4,5)) { # the SEQUENCE is defined (numbers 1 to 5) and loop is opened with \"{\"\n print(num + 2) # The OPERATIONS (add two to each sequence number and print)\n} # The loop is closed with \"}\" \n\n[1] 3\n[1] 4\n[1] 5\n[1] 6\n[1] 7\n\n # There is no \"container\" in this example\n\n\n\nSequence\nThis is the “for” part of a for loop - the operations will run “for” each item in the sequence. The sequence can be a series of values (e.g. names of jurisdictions, diseases, column names, list elements, etc), or it can be a series of consecutive numbers (e.g. 1,2,3,4,5). Each approach has their own utilities, described below.\nThe basic structure of a sequence statement is item in vector.\n\nYou can write any character or word in place of “item” (e.g. “i”, “num”, “hosp”, “district”, etc.). The value of this “item” changes with each iteration of the loop, proceeding through each value in the vector.\n\nThe vector could be of character values, column names, or perhaps a sequence of numbers - these are the values that will change with each iteration. You can use them within the for loop operations using the “item” term.\n\nExample: sequence of character values\nIn this example, a loop is performed for each value in a pre-defined character vector of hospital names.\n\n# make vector of the hospital names\nhospital_names <- unique(linelist$hospital)\nhospital_names # print\n\n[1] \"Other\" \n[2] \"Missing\" \n[3] \"St. Mark's Maternity Hospital (SMMH)\"\n[4] \"Port Hospital\" \n[5] \"Military Hospital\" \n[6] \"Central Hospital\" \n\n\nWe have chosen the term hosp to represent values from the vector hospital_names. For the first iteration of the loop, the value of hosp will be hospital_names[[1]]. For the second loop it will be hospital_names[[2]]. And so on…\n\n# a 'for loop' with character sequence\n\nfor (hosp in hospital_names){ # sequence\n \n # OPERATIONS HERE\n }\n\nExample: sequence of column names\nThis is a variation on the character sequence above, in which the names of an existing R object are extracted and become the vector. For example, the column names of a data frame. Conveniently, in the operations code of the for loop, the column names can be used to index (subset) their original data frame\nBelow, the sequence is the names() (column names) of the linelist data frame. Our “item” name is col, which will represent each column name as the loops proceeds.\nFor purposes of example, we include operations code inside the for loop, which is run for every value in the sequence. In this code, the sequence values (column names) are used to index (subset) linelist, one-at-a-time. As taught in the R basics page, double branckets [[ ]] are used to subset. The resulting column is passed to is.na(), then to sum() to produce the number of values in the column that are missing. The result is printed to the console - one number for each column.\nA note on indexing with column names - whenever referencing the column itself do not just write “col”! col represents just the character column name! To refer to the entire column you must use the column name as an index on linelist via linelist[[col]].\n\nfor (col in names(linelist)){ # loop runs for each column in linelist; column name represented by \"col\" \n \n # Example operations code - print number of missing values in column\n print(sum(is.na(linelist[[col]]))) # linelist is indexed by current value of \"col\"\n \n}\n\n[1] 0\n[1] 0\n[1] 2087\n[1] 256\n[1] 0\n[1] 936\n[1] 1323\n[1] 278\n[1] 86\n[1] 0\n[1] 86\n[1] 86\n[1] 86\n[1] 0\n[1] 0\n[1] 0\n[1] 2088\n[1] 2088\n[1] 0\n[1] 0\n[1] 0\n[1] 249\n[1] 249\n[1] 249\n[1] 249\n[1] 249\n[1] 149\n[1] 765\n[1] 0\n[1] 256\n\n\nSequence of numbers\nIn this approach, the sequence is a series of consecutive numbers. Thus, the value of the “item” is not a character value (e.g. “Central Hospital” or “date_onset”) but is a number. This is useful for looping through data frames, as you can use the “item” number inside the for loop to index the data frame by row number.\nFor example, let’s say that you want to loop through every row in your data frame and extract certain information. Your “items” would be numeric row numbers. Often, “items” in this case are written as i.\nThe for loop process could be explained in words as “for every item in a sequence of numbers from 1 to the total number of rows in my data frame, do X”. For the first iteration of the loop, the value of “item” i would be 1. For the second iteration, i would be 2, etc.\nHere is what the sequence looks like in code: for (i in 1:nrow(linelist)) {OPERATIONS CODE} where i represents the “item” and 1:nrow(linelist) produces a sequence of consecutive numbers from 1 through the number of rows in linelist.\n\nfor (i in 1:nrow(linelist)) { # use on a data frame\n # OPERATIONS HERE\n} \n\nIf you want the sequence to be numbers, but you are starting from a vector (not a data frame), use the shortcut seq_along() to return a sequence of numbers for each element in the vector. For example, for (i in seq_along(hospital_names) {OPERATIONS CODE}.\nThe below code actually returns numbers, which would become the value of i in their respective loop.\n\nseq_along(hospital_names) # use on a named vector\n\n[1] 1 2 3 4 5 6\n\n\nOne advantage of using numbers in the sequence is that is easy to also use the i number to index a container that stores the loop outputs. There is an example of this in the Operations section below.\n\n\nOperations\nThis is code within the curly brackets { } of the for loop. You want this code to run for each “item” in the sequence. Therefore, be careful that every part of your code that changes by the “item” is correctly coded such that it actually changes! E.g. remember to use [[ ]] for indexing.\nIn the example below, we iterate through each row in the linelist. The gender and age values of each row are pasted together and stored in the container character vector cases_demographics. Note how we also use indexing [[i]] to save the loop output to the correct position in the “container” vector.\n\n# create container to store results - a character vector\ncases_demographics <- vector(mode = \"character\", length = nrow(linelist))\n\n# the for loop\nfor (i in 1:nrow(linelist)){\n \n # OPERATIONS\n # extract values from linelist for row i, using brackets for indexing\n row_gender <- linelist$gender[[i]]\n row_age <- linelist$age_years[[i]] # don't forget to index!\n \n # combine gender-age and store in container vector at indexed location\n cases_demographics[[i]] <- str_c(row_gender, row_age, sep = \",\") \n\n} # end for loop\n\n\n# display first 10 rows of container\nhead(cases_demographics, 10)\n\n [1] \"m,2\" \"f,3\" \"m,56\" \"f,18\" \"m,3\" \"f,16\" \"f,16\" \"f,0\" \"m,61\" \"f,27\"\n\n\n\n\nContainer\nSometimes the results of your for loop will be printed to the console or RStudio Plots pane. Other times, you will want to store the outputs in a “container” for later use. Such a container could be a vector, a data frame, or even a list.\nIt is most efficient to create the container for the results before even beginning the for loop. In practice, this means creating an empty vector, data frame, or list. These can be created with the functions vector() for vectors or lists, or with matrix() and data.frame() for a data frame.\nEmpty vector\nUse vector() and specify the mode = based on the expected class of the objects you will insert - either “double” (to hold numbers), “character”, or “logical”. You should also set the length = in advance. This should be the length of your for loop sequence.\nSay you want to store the median delay-to-admission for each hospital. You would use “double” and set the length to be the number of expected outputs (the number of unique hospitals in the data set).\n\ndelays <- vector(\n mode = \"double\", # we expect to store numbers\n length = length(unique(linelist$hospital))) # the number of unique hospitals in the dataset\n\nEmpty data frame\nYou can make an empty data frame by specifying the number of rows and columns like this:\n\ndelays <- data.frame(matrix(ncol = 2, nrow = 3))\n\nEmpty list\nYou may want store some plots created by a for loop in a list. A list is like vector, but holds other R objects within it that can be of different classes. Items in a list could be a single number, a dataframe, a vector, and even another list.\nYou actually initialize an empty list using the same vector() command as above, but with mode = \"list\". Specify the length however you wish.\n\nplots <- vector(mode = \"list\", length = 16)\n\n\n\nPrinting\nNote that to print from within a for loop you will likely need to explicitly wrap with the function print().\nIn this example below, the sequence is an explicit character vector, which is used to subset the linelist by hospital. The results are not stored in a container, but rather are printed to console with the print() function.\n\nfor (hosp in hospital_names){ \n hospital_cases <- linelist %>% filter(hospital == hosp)\n print(nrow(hospital_cases))\n}\n\n[1] 885\n[1] 1469\n[1] 422\n[1] 1762\n[1] 896\n[1] 454\n\n\n\n\nTesting your for loop\nTo test your loop, you can run a command to make a temporary assignment of the “item”, such as i <- 10 or hosp <- \"Central Hospital\". Do this outside the loop and then run your operations code only (the code within the curly brackets) to see if the expected results are produced.\n\n\nLooping plots\nTo put all three components together (container, sequence, and operations) let’s try to plot an epicurve for each hospital (see page on Epidemic curves).\nWe can make a nice epicurve of all the cases by gender using the incidence2 package as below:\n\n# create 'incidence' object\noutbreak <- incidence2::incidence( \n x = linelist, # dataframe - complete linelist\n date_index = \"date_onset\", # date column\n interval = \"week\", # aggregate counts weekly\n groups = \"gender\") # group values by gender\n #na_as_group = TRUE) # missing gender is own group\n\n# tracer la courbe d'épidémie\nggplot(outbreak, # nom de l'objet d'incidence\n aes(x = date_index, #aesthetiques et axes\n y = count, \n fill = gender), # Fill colour of bars by gender\n color = \"black\" # Contour colour of bars\n ) + \n geom_col() + \n facet_wrap(~gender) +\n theme_bw() + \n labs(title = \"Outbreak of all cases\", #titre\n x = \"Counts\", \n y = \"Date\", \n fill = \"Gender\", \n color = \"Gender\")\n\n\n\n\n\n\n\n\nTo produce a separate plot for each hospital’s cases, we can put this epicurve code within a for loop.\nFirst, we save a named vector of the unique hospital names, hospital_names. The for loop will run once for each of these names: for (hosp in hospital_names). Each iteration of the for loop, the current hospital name from the vector will be represented as hosp for use within the loop.\nWithin the loop operations, you can write R code as normal, but use the “item” (hosp in this case) knowing that its value will be changing. Within this loop:\n\nA filter() is applied to linelist, such that column hospital must equal the current value of hosp\n\nThe incidence object is created on the filtered linelist\n\nThe plot for the current hospital is created, with an auto-adjusting title that uses hosp\n\nThe plot for the current hospital is temporarily saved and then printed\n\nThe loop then moves onward to repeat with the next hospital in hospital_names\n\n\n# make vector of the hospital names\nhospital_names <- unique(linelist$hospital)\n\n# for each name (\"hosp\") in hospital_names, create and print the epi curve\nfor (hosp in hospital_names) {\n \n # create incidence object specific to the current hospital\n outbreak_hosp <- incidence2::incidence(\n x = linelist %>% filter(hospital == hosp), # linelist is filtered to the current hospital\n date_index = \"date_onset\",\n interval = \"week\", \n groups = \"gender\"#,\n #na_as_group = TRUE\n )\n \n plot_hosp <- ggplot(outbreak_hosp, # incidence object name\n aes(x = date_index, #axes\n y = count, \n fill = gender), # fill colour by gender\n color = \"black\" # colour of bar contour\n ) + \n geom_col() + \n facet_wrap(~gender) +\n theme_bw() + \n labs(title = stringr::str_glue(\"Epidemic of cases admitted to {hosp}\"), #title\n x = \"Counts\", \n y = \"Date\", \n fill = \"Gender\", \n color = \"Gender\")\n \n # With older versions of R, remove the # before na_as_group and use this plot command instead.\n # plot_hosp <- plot(\n# outbreak_hosp,\n# fill = \"gender\",\n# color = \"black\",\n# title = stringr::str_glue(\"Epidemic of cases admitted to {hosp}\")\n# )\n \n #print the plot for hospitals\n print(plot_hosp)\n \n} # end the for loop when it has been run for every hospital in hospital_names \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTracking progress of a loop\nA loop with many iterations can run for many minutes or even hours. Thus, it can be helpful to print the progress to the R console. The if statement below can be placed within the loop operations to print every 100th number. Just adjust it so that i is the “item” in your loop.\n\n# loop with code to print progress every 100 iterations\nfor (i in seq_len(nrow(linelist))){\n\n # print progress\n if(i %% 100==0){ # The %% operator is the remainder\n print(i)\n\n}", + "text": "16.2 for loops\n\nfor loops in R\nfor loops are not emphasized in R, but are common in other programming languages. As a beginner, they can be helpful to learn and practice with because they are easier to “explore”, “de-bug”, and otherwise grasp exactly what is happening for each iteration, especially when you are not yet comfortable writing your own functions.\nYou may move quickly through for loops to iterating with mapped functions with purrr (see section below).\n\n\nCore components\nA for loop has three core parts:\n\nThe sequence of items to iterate through.\n\nThe operations to conduct per item in the sequence.\n\nThe container for the results (optional).\n\nThe basic syntax is: for (item in sequence) {do operations using item}. Note the parentheses and the curly brackets. The results could be printed to console, or stored in a container R object.\nA simple for loop example is below.\n\nfor (num in c(1, 2, 3, 4, 5)) { # the SEQUENCE is defined (numbers 1 to 5) and loop is opened with \"{\"\n print(num + 2) # The OPERATIONS (add two to each sequence number and print)\n} # The loop is closed with \"}\" \n\n[1] 3\n[1] 4\n[1] 5\n[1] 6\n[1] 7\n\n # There is no \"container\" in this example\n\n\n\nSequence\nThis is the “for” part of a for loop - the operations will run “for” each item in the sequence. The sequence can be a series of values (e.g. names of jurisdictions, diseases, column names, list elements, etc), or it can be a series of consecutive numbers (e.g. 1, 2, 3, 4, 5). Each approach has their own utilities, described below.\nThe basic structure of a sequence statement is item in vector.\n\nYou can write any character or word in place of “item” (e.g. “i”, “num”, “hosp”, “district”, etc.). The value of this “item” changes with each iteration of the loop, proceeding through each value in the vector.\n\nThe vector could be of character values, column names, or perhaps a sequence of numbers - these are the values that will change with each iteration. You can use them within the for loop operations using the “item” term.\n\nExample: sequence of character values\nIn this example, a loop is performed for each value in a pre-defined character vector of hospital names.\n\n# make vector of the hospital names\nhospital_names <- unique(linelist$hospital)\nhospital_names # print\n\n[1] \"Other\" \n[2] \"Missing\" \n[3] \"St. Mark's Maternity Hospital (SMMH)\"\n[4] \"Port Hospital\" \n[5] \"Military Hospital\" \n[6] \"Central Hospital\" \n\n\nWe have chosen the term hosp to represent values from the vector hospital_names. For the first iteration of the loop, the value of hosp will be hospital_names[[1]]. For the second loop it will be hospital_names[[2]]. And so on.\n\n# a 'for loop' with character sequence\n\nfor (hosp in hospital_names){ # sequence\n \n # OPERATIONS HERE\n }\n\nExample: sequence of column names\nThis is a variation on the character sequence above, in which the names of an existing R object are extracted and become the vector. For example, the column names of a data frame. Conveniently, in the operations code of the for loop, the column names can be used to index (subset) their original data frame\nBelow, the sequence is the names() (column names) of the linelist data frame. Our “item” name is col, which will represent each column name as the loops proceeds.\nFor purposes of example, we include operations code inside the for loop, which is run for every value in the sequence. In this code, the sequence values (column names) are used to index (subset) linelist, one-at-a-time. As taught in the R basics page, double branckets [[ ]] are used to subset. The resulting column is passed to is.na(), then to sum() to produce the number of values in the column that are missing. The result is printed to the console - one number for each column.\nA note on indexing with column names - whenever referencing the column itself do not just write “col”! col represents just the character column name! To refer to the entire column you must use the column name as an index on linelist via linelist[[col]].\n\nfor (col in names(linelist)){ # loop runs for each column in linelist; column name represented by \"col\" \n \n # Example operations code - print number of missing values in column\n print(sum(is.na(linelist[[col]]))) # linelist is indexed by current value of \"col\"\n \n}\n\n[1] 0\n[1] 0\n[1] 2087\n[1] 256\n[1] 0\n[1] 936\n[1] 1323\n[1] 278\n[1] 86\n[1] 0\n[1] 86\n[1] 86\n[1] 86\n[1] 0\n[1] 0\n[1] 0\n[1] 2088\n[1] 2088\n[1] 0\n[1] 0\n[1] 0\n[1] 249\n[1] 249\n[1] 249\n[1] 249\n[1] 249\n[1] 149\n[1] 765\n[1] 0\n[1] 256\n\n\nSequence of numbers\nIn this approach, the sequence is a series of consecutive numbers. Thus, the value of the “item” is not a character value (e.g. “Central Hospital” or “date_onset”) but is a number. This is useful for looping through data frames, as you can use the “item” number inside the for loop to index the data frame by row number.\nFor example, let’s say that you want to loop through every row in your data frame and extract certain information. Your “items” would be numeric row numbers. Often, “items” in this case are written as i.\nThe for loop process could be explained in words as “for every item in a sequence of numbers from 1 to the total number of rows in my data frame, do X”. For the first iteration of the loop, the value of “item” i would be 1. For the second iteration, i would be 2, etc.\nHere is what the sequence looks like in code: for (i in 1:nrow(linelist)) {OPERATIONS CODE} where i represents the “item” and 1:nrow(linelist) produces a sequence of consecutive numbers from 1 through the number of rows in linelist.\n\nfor (i in 1:nrow(linelist)) { # use on a data frame\n # OPERATIONS HERE\n} \n\nIf you want the sequence to be numbers, but you are starting from a vector (not a data frame), use the shortcut seq_along() to return a sequence of numbers for each element in the vector. For example, for (i in seq_along(hospital_names) {OPERATIONS CODE}.\nThe below code actually returns numbers, which would become the value of i in their respective loop.\n\nseq_along(hospital_names) # use on a named vector\n\n[1] 1 2 3 4 5 6\n\n\nOne advantage of using numbers in the sequence is that is easy to also use the i number to index a container that stores the loop outputs. There is an example of this in the Operations section below.\n\n\nOperations\nThis is code within the curly brackets { } of the for loop. You want this code to run for each “item” in the sequence. Therefore, be careful that every part of your code that changes by the “item” is correctly coded such that it actually changes! E.g. remember to use [[ ]] for indexing.\nIn the example below, we iterate through each row in the linelist. The gender and age values of each row are pasted together and stored in the container character vector cases_demographics. Note how we also use indexing [[i]] to save the loop output to the correct position in the “container” vector.\n\n# create container to store results - a character vector\ncases_demographics <- vector(mode = \"character\", length = nrow(linelist))\n\n# the for loop\nfor (i in 1:nrow(linelist)){\n \n # OPERATIONS\n # extract values from linelist for row i, using brackets for indexing\n row_gender <- linelist$gender[[i]]\n row_age <- linelist$age_years[[i]] # don't forget to index!\n \n # combine gender-age and store in container vector at indexed location\n cases_demographics[[i]] <- str_c(row_gender, row_age, sep = \",\") \n\n} # end for loop\n\n\n# display first 10 rows of container\nhead(cases_demographics, 10)\n\n [1] \"m,2\" \"f,3\" \"m,56\" \"f,18\" \"m,3\" \"f,16\" \"f,16\" \"f,0\" \"m,61\" \"f,27\"\n\n\n\n\nContainer\nSometimes the results of your for loop will be printed to the console or RStudio Plots pane. Other times, you will want to store the outputs in a “container” for later use. Such a container could be a vector, a data frame, or even a list.\nIt is most efficient to create the container for the results before even beginning the for loop. In practice, this means creating an empty vector, data frame, or list. These can be created with the functions vector() for vectors or lists, or with matrix() and data.frame() for a data frame.\nEmpty vector\nUse vector() and specify the mode = based on the expected class of the objects you will insert - either “double” (to hold numbers), “character”, or “logical”. You should also set the length = in advance. This should be the length of your for loop sequence.\nSay you want to store the median delay-to-admission for each hospital. You would use “double” and set the length to be the number of expected outputs (the number of unique hospitals in the data set).\n\ndelays <- vector(\n mode = \"double\", # we expect to store numbers\n length = length(unique(linelist$hospital)) # the number of unique hospitals in the dataset\n )\n\nEmpty data frame\nYou can make an empty data frame by specifying the number of rows and columns like this:\n\ndelays <- data.frame(matrix(ncol = 2, nrow = 3))\n\nEmpty list\nYou may want store some plots created by a for loop in a list. A list is like vector, but holds other R objects within it that can be of different classes. Items in a list could be a single number, a dataframe, a vector, and even another list.\nYou actually initialize an empty list using the same vector() command as above, but with mode = \"list\". Specify the length however you wish.\n\nplots <- vector(mode = \"list\", length = 16)\n\n\n\nPrinting\nNote that to print from within a for loop you will likely need to explicitly wrap with the function print().\nIn this example below, the sequence is an explicit character vector, which is used to subset the linelist by hospital. The results are not stored in a container, but rather are printed to console with the print() function.\n\nfor (hosp in hospital_names){ \n hospital_cases <- linelist %>% filter(hospital == hosp)\n print(nrow(hospital_cases))\n}\n\n[1] 885\n[1] 1469\n[1] 422\n[1] 1762\n[1] 896\n[1] 454\n\n\n\n\nTesting your for loop\nTo test your loop, you can run a command to make a temporary assignment of the “item”, such as i <- 10 or hosp <- \"Central Hospital\". Do this outside the loop and then run your operations code only (the code within the curly brackets) to see if the expected results are produced.\n\n\nLooping plots\nTo put all three components together (container, sequence, and operations) let’s try to plot an epicurve for each hospital (see page on Epidemic curves).\nWe can make a nice epicurve of all the cases by gender using the incidence2 package as below:\n\n# create 'incidence' object\noutbreak <- incidence2::incidence( \n x = linelist, # dataframe - complete linelist\n date_index = \"date_onset\", # date column\n interval = \"week\", # aggregate counts weekly\n groups = \"gender\" # group values by gender\n ) \n #na_as_group = TRUE) # missing gender is own group\n\n# plot\nggplot(outbreak, \n aes(x = date_index, \n y = count, \n fill = gender), # Fill colour of bars by gender\n color = \"black\" # Contour colour of bars\n ) + \n geom_col() + \n facet_wrap(~gender) +\n theme_bw() + \n labs(title = \"Outbreak of all cases\", #titre\n x = \"Counts\", \n y = \"Date\", \n fill = \"Gender\", \n color = \"Gender\") +\n theme(axis.title.x = element_blank(),\n axis.text.x = element_blank(),\n axis.ticks.x = element_blank())\n\n\n\n\n\n\n\n\nTo produce a separate plot for each hospital’s cases, we can put this epicurve code within a for loop.\nFirst, we save a named vector of the unique hospital names, hospital_names. The for loop will run once for each of these names: for (hosp in hospital_names). Each iteration of the for loop, the current hospital name from the vector will be represented as hosp for use within the loop.\nWithin the loop operations, you can write R code as normal, but use the “item” (hosp in this case) knowing that its value will be changing. Within this loop:\n\nA filter() is applied to linelist, such that column hospital must equal the current value of hosp.\n\nThe incidence object is created on the filtered linelist.\n\nThe plot for the current hospital is created, with an auto-adjusting title that uses hosp.\n\nThe plot for the current hospital is temporarily saved and then printed.\n\nThe loop then moves onward to repeat with the next hospital in hospital_names.\n\n\n# make vector of the hospital names\nhospital_names <- unique(linelist$hospital)\n\n# for each name (\"hosp\") in hospital_names, create and print the epi curve\nfor (hosp in hospital_names) {\n \n # create incidence object specific to the current hospital\n outbreak_hosp <- incidence2::incidence(\n x = linelist %>% filter(hospital == hosp), # linelist is filtered to the current hospital\n date_index = \"date_onset\",\n interval = \"week\", \n groups = \"gender\"#,\n #na_as_group = TRUE\n )\n \n plot_hosp <- ggplot(outbreak_hosp, # incidence object name\n aes(x = date_index, #axes\n y = count, \n fill = gender), # fill colour by gender\n color = \"black\" # colour of bar contour\n ) + \n geom_col() + \n facet_wrap(~gender) +\n theme_bw() + \n labs(title = stringr::str_glue(\"Epidemic of cases admitted to {hosp}\"), #title\n x = \"Counts\", \n y = \"Date\", \n fill = \"Gender\", \n color = \"Gender\")\n \n #print the plot for hospitals\n print(plot_hosp)\n \n} # end the for loop when it has been run for every hospital in hospital_names \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTracking progress of a loop\nA loop with many iterations can run for many minutes or even hours. Thus, it can be helpful to print the progress to the R console. The if statement below can be placed within the loop operations to print every 100th number. Just adjust it so that i is the “item” in your loop.\n\n# loop with code to print progress every 100 iterations\nfor (i in seq_len(nrow(linelist))){\n\n # print progress\n if(i %% 100==0){ # The %% operator is the remainder\n print(i)\n\n}", "crumbs": [ "Data Management", "16  Iteration, loops, and lists" @@ -1484,7 +1484,7 @@ "href": "new_pages/iteration.html#iter_purrr", "title": "16  Iteration, loops, and lists", "section": "16.3 purrr and lists", - "text": "16.3 purrr and lists\nAnother approach to iterative operations is the purrr package - it is the tidyverse approach to iteration.\nIf you are faced with performing the same task several times, it is probably worth creating a generalised solution that you can use across many inputs. For example, producing plots for multiple jurisdictions, or importing and combining many files.\nThere are also a few other advantages to purrr - you can use it with pipes %>%, it handles errors better than normal for loops, and the syntax is quite clean and simple! If you are using a for loop, you can probably do it more clearly and succinctly with purrr!\nKeep in mind that purrr is a functional programming tool. That is, the operations that are to be iteratively applied are wrapped up into functions. See the Writing functions page to learn how to write your own functions.\npurrr is also almost entirely based around lists and vectors - so think about it as applying a function to each element of that list/vector!\n\nLoad packages\npurrr is part of the tidyverse, so there is no need to install/load a separate package.\n\npacman::p_load(\n rio, # import/export\n here, # relative filepaths\n tidyverse, # data mgmt and viz\n writexl, # write Excel file with multiple sheets\n readxl # import Excel with multiple sheets\n)\n\n\n\nmap()\nOne core purrr function is map(), which “maps” (applies) a function to each input element of a list/vector you provide.\nThe basic syntax is map(.x = SEQUENCE, .f = FUNCTION, OTHER ARGUMENTS). In a bit more detail:\n\n.x = are the inputs upon which the .f function will be iteratively applied - e.g. a vector of jurisdiction names, columns in a data frame, or a list of data frames\n\n.f = is the function to apply to each element of the .x input - it could be a function like print() that already exists, or a custom function that you define. The function is often written after a tilde ~ (details below).\n\nA few more notes on syntax:\n\nIf the function needs no further arguments specified, it can be written with no parentheses and no tilde (e.g. .f = mean). To provide arguments that will be the same value for each iteration, provide them within map() but outside the .f = argument, such as the na.rm = T in map(.x = my_list, .f = mean, na.rm=T).\n\nYou can use .x (or simply .) within the .f = function as a placeholder for the .x value of that iteration\n\nUse tilde syntax (~) to have greater control over the function - write the function as normal with parentheses, such as: map(.x = my_list, .f = ~mean(., na.rm = T)). Use this syntax particularly if the value of an argument will change each iteration, or if it is the value .x itself (see examples below)\n\nThe output of using map() is a list - a list is an object class like a vector but whose elements can be of different classes. So, a list produced by map() could contain many data frames, or many vectors, many single values, or even many lists! There are alternative versions of map() explained below that produce other types of outputs (e.g. map_dfr() to produce a data frame, map_chr() to produce character vectors, and map_dbl() to produce numeric vectors).\n\nExample - import and combine Excel sheets\nLet’s demonstrate with a common epidemiologist task: - You want to import an Excel workbook with case data, but the data are split across different named sheets in the workbook. How do you efficiently import and combine the sheets into one data frame?\nLet’s say we are sent the below Excel workbook. Each sheet contains cases from a given hospital.\n\n\n\n\n\n\n\n\n\nHere is one approach that uses map():\n\nmap() the function import() so that it runs for each Excel sheet\n\nCombine the imported data frames into one using bind_rows()\n\nAlong the way, preserve the original sheet name for each row, storing this information in a new column in the final data frame\n\nFirst, we need to extract the sheet names and save them. We provide the Excel workbook’s file path to the function excel_sheets() from the package readxl, which extracts the sheet names. We store them in a character vector called sheet_names.\n\nsheet_names <- readxl::excel_sheets(\"hospital_linelists.xlsx\")\n\nHere are the names:\n\nsheet_names\n\n[1] \"Central Hospital\" \"Military Hospital\" \n[3] \"Missing\" \"Other\" \n[5] \"Port Hospital\" \"St. Mark's Maternity Hospital\"\n\n\nNow that we have this vector of names, map() can provide them one-by-one to the function import(). In this example, the sheet_names are .x and import() is the function .f.\nRecall from the Import and export page that when used on Excel workbooks, import() can accept the argument which = specifying the sheet to import. Within the .f function import(), we provide which = .x, whose value will change with each iteration through the vector sheet_names - first “Central Hospital”, then “Military Hospital”, etc.\nOf note - because we have used map(), the data in each Excel sheet will be saved as a separate data frame within a list. We want each of these list elements (data frames) to have a name, so before we pass sheet_names to map() we pass it through set_names() from purrr, which ensures that each list element gets the appropriate name.\nWe save the output list as combined.\n\ncombined <- sheet_names %>% \n purrr::set_names() %>% \n map(.f = ~import(\"hospital_linelists.xlsx\", which = .x))\n\nWhen we inspect output, we see that the data from each Excel sheet is saved in the list with a name. This is good, but we are not quite finished.\n\n\n\n\n\n\n\n\n\nLastly, we use the function bind_rows() (from dplyr) which accepts the list of similarly-structured data frames and combines them into one data frame. To create a new column from the list element names, we use the argument .id = and provide it with the desired name for the new column.\nBelow is the whole sequence of commands:\n\nsheet_names <- readxl::excel_sheets(\"hospital_linelists.xlsx\") # extract sheet names\n \ncombined <- sheet_names %>% # begin with sheet names\n purrr::set_names() %>% # set their names\n map(.f = ~import(\"hospital_linelists.xlsx\", which = .x)) %>% # iterate, import, save in list\n bind_rows(.id = \"origin_sheet\") # combine list of data frames, preserving origin in new column \n\nAnd now we have one data frame with a column containing the sheet of origin!\n\n\n\n\n\n\n\n\n\nThere are variations of map() that you should be aware of. For example, map_dfr() returns a data frame, not a list. Thus, we could have used it for the task above and not have had to bind rows. But then we would not have been able to capture which sheet (hospital) each case came from.\nOther variations include map_chr(), map_dbl(). These are very useful functions for two reasons. Firstly. they automatically convert the output of an iterative function into a vector (not a list). Secondly, they can explicitly control the class that the data comes back in - you ensure that your data comes back as a character vector with map_chr(), or numeric vector with map_dbl(). Lets return to these later in the section!\nThe functions map_at() and map_if() are also very useful for iteration - they allow you to specify which elements of a list you should iterate at! These work by simply applying a vector of indexes/names (in the case of map_at()) or a logical test (in the case of map_if()).\nLets use an example where we didn’t want to read the first sheet of hospital data. We use map_at() instead of map(), and specify the .at = argument to c(-1) which means to not use the first element of .x. Alternatively, you can provide a vector of positive numbers, or names, to .at = to specify which elements to use.\n\nsheet_names <- readxl::excel_sheets(\"hospital_linelists.xlsx\")\n\ncombined <- sheet_names %>% \n purrr::set_names() %>% \n # exclude the first sheet\n map_at(.f = ~import( \"hospital_linelists.xlsx\", which = .x),\n .at = c(-1))\n\nNote that the first sheet name will still appear as an element of the output list - but it is only a single character name (not a data frame). You would need to remove this element before binding rows. We will cover how to remove and modify list elements in a later section.\n\n\n\nSplit dataset and export\nBelow, we give an example of how to split a dataset into parts and then use map() iteration to export each part as a separate Excel sheet, or as a separate CSV file.\n\nSplit dataset\nLet’s say we have the complete case linelist as a data frame, and we now want to create a separate linelist for each hospital and export each as a separate CSV file. Below, we do the following steps:\nUse group_split() (from dplyr) to split the linelist data frame by unique values in column hospital. The output is a list containing one data frame per hospital subset.\n\nlinelist_split <- linelist %>% \n group_split(hospital)\n\nWe can run View(linelist_split) and see that this list contains 6 data frames (“tibbles”), each representing the cases from one hospital.\n\n\n\n\n\n\n\n\n\nHowever, note that the data frames in the list do not have names by default! We want each to have a name, and then to use that name when saving the CSV file.\nOne approach to extracting the names is to use pull() (from dplyr) to extract the hospital column from each data frame in the list. Then, to be safe, we convert the values to character and then use unique() to get the name for that particular data frame. All of these steps are applied to each data frame via map().\n\nnames(linelist_split) <- linelist_split %>% # Assign to names of listed data frames \n # Extract the names by doing the following to each data frame: \n map(.f = ~pull(.x, hospital)) %>% # Pull out hospital column\n map(.f = ~as.character(.x)) %>% # Convert to character, just in case\n map(.f = ~unique(.x)) # Take the unique hospital name\n\nWe can now see that each of the list elements has a name. These names can be accessed via names(linelist_split).\n\n\n\n\n\n\n\n\n\n\nnames(linelist_split)\n\n[1] \"Central Hospital\" \n[2] \"Military Hospital\" \n[3] \"Missing\" \n[4] \"Other\" \n[5] \"Port Hospital\" \n[6] \"St. Mark's Maternity Hospital (SMMH)\"\n\n\n\nMore than one group_split() column\nIf you wanted to split the linelist by more than one grouping column, such as to produce subset linelist by intersection of hospital AND gender, you will need a different approach to naming the list elements. This involves collecting the unique “group keys” using group_keys() from dplyr - they are returned as a data frame. Then you can combine the group keys into values with unite() as shown below, and assign these conglomerate names to linelist_split.\n\n# split linelist by unique hospital-gender combinations\nlinelist_split <- linelist %>% \n group_split(hospital, gender)\n\n# extract group_keys() as a dataframe\ngroupings <- linelist %>% \n group_by(hospital, gender) %>% \n group_keys()\n\ngroupings # show unique groupings \n\n# A tibble: 18 × 2\n hospital gender\n <chr> <chr> \n 1 Central Hospital f \n 2 Central Hospital m \n 3 Central Hospital <NA> \n 4 Military Hospital f \n 5 Military Hospital m \n 6 Military Hospital <NA> \n 7 Missing f \n 8 Missing m \n 9 Missing <NA> \n10 Other f \n11 Other m \n12 Other <NA> \n13 Port Hospital f \n14 Port Hospital m \n15 Port Hospital <NA> \n16 St. Mark's Maternity Hospital (SMMH) f \n17 St. Mark's Maternity Hospital (SMMH) m \n18 St. Mark's Maternity Hospital (SMMH) <NA> \n\n\nNow we combine the groupings together, separated by dashes, and assign them as the names of list elements in linelist_split. This takes some extra lines as we replace NA with “Missing”, use unite() from dplyr to combine the column values together (separated by dashes), and then convert into an un-named vector so it can be used as names of linelist_split.\n\n# Combine into one name value \nnames(linelist_split) <- groupings %>% \n mutate(across(everything(), replace_na, \"Missing\")) %>% # replace NA with \"Missing\" in all columns\n unite(\"combined\", sep = \"-\") %>% # Unite all column values into one\n setNames(NULL) %>% \n as_vector() %>% \n as.list()\n\n\n\n\nExport as Excel sheets\nTo export the hospital linelists as an Excel workbook with one linelist per sheet, we can just provide the named list linelist_split to the write_xlsx() function from the writexl package. This has the ability to save one Excel workbook with multiple sheets. The list element names are automatically applied as the sheet names.\n\nlinelist_split %>% \n writexl::write_xlsx(path = here(\"data\", \"hospital_linelists.xlsx\"))\n\nYou can now open the Excel file and see that each hospital has its own sheet.\n\n\n\n\n\n\n\n\n\n\n\nExport as CSV files\nIt is a bit more complex command, but you can also export each hospital-specific linelist as a separate CSV file, with a file name specific to the hospital.\nAgain we use map(): we take the vector of list element names (shown above) and use map() to iterate through them, applying export() (from the rio package, see Import and export page) on the data frame in the list linelist_split that has that name. We also use the name to create a unique file name. Here is how it works:\n\nWe begin with the vector of character names, passed to map() as .x\n\nThe .f function is export() , which requires a data frame and a file path to write to\n\nThe input .x (the hospital name) is used within .f to extract/index that specific element of linelist_split list. This results in only one data frame at a time being provided to export().\n\nFor example, when map() iterates for “Military Hospital”, then linelist_split[[.x]] is actually linelist_split[[\"Military Hospital\"]], thus returning the second element of linelist_split - which is all the cases from Military Hospital.\n\nThe file path provided to export() is dynamic via use of str_glue() (see Characters and strings page):\n\nhere() is used to get the base of the file path and specify the “data” folder (note single quotes to not interrupt the str_glue() double quotes)\n\n\nThen a slash /, and then again the .x which prints the current hospital name to make the file identifiable\n\nFinally the extension “.csv” which export() uses to create a CSV file\n\n\nnames(linelist_split) %>%\n map(.f = ~export(linelist_split[[.x]], file = str_glue(\"{here('data')}/{.x}.csv\")))\n\nNow you can see that each file is saved in the “data” folder of the R Project “Epi_R_handbook”!\n\n\n\n\n\n\n\n\n\n\n\n\nCustom functions\nYou may want to create your own function to provide to map().\nLet’s say we want to create epidemic curves for each hospital’s cases. To do this using purrr, our .f function can be ggplot() and extensions with + as usual. As the output of map() is always a list, the plots are stored in a list. Because they are plots, they can be extracted and plotted with the ggarrange() function from the ggpubr package (documentation).\n\n# load package for plotting elements from list\npacman::p_load(ggpubr)\n\n# map across the vector of 6 hospital \"names\" (created earlier)\n# use the ggplot function specified\n# output is a list with 6 ggplots\n\nhospital_names <- unique(linelist$hospital)\n\nmy_plots <- map(\n .x = hospital_names,\n .f = ~ggplot(data = linelist %>% filter(hospital == .x)) +\n geom_histogram(aes(x = date_onset)) +\n labs(title = .x)\n)\n\n# print the ggplots (they are stored in a list)\nggarrange(plotlist = my_plots, ncol = 2, nrow = 3)\n\n\n\n\n\n\n\n\nIf this map() code looks too messy, you can achieve the same result by saving your specific ggplot() command as a custom user-defined function, for example we can name it make_epicurve()). This function is then used within the map(). .x will be iteratively replaced by the hospital name, and used as hosp_name in the make_epicurve() function. See the page on Writing functions.\n\n# Create function\nmake_epicurve <- function(hosp_name){\n \n ggplot(data = linelist %>% filter(hospital == hosp_name)) +\n geom_histogram(aes(x = date_onset)) +\n theme_classic()+\n labs(title = hosp_name)\n \n}\n\n\n# mapping\nmy_plots <- map(hospital_names, ~make_epicurve(hosp_name = .x))\n\n# print the ggplots (they are stored in a list)\nggarrange(plotlist = my_plots, ncol = 2, nrow = 3)\n\n\n\nMapping a function across columns\nAnother common use-case is to map a function across many columns. Below, we map() the function t.test() across numeric columns in the data frame linelist, comparing the numeric values by gender.\nRecall from the page on Simple statistical tests that t.test() can take inputs in a formula format, such as t.test(numeric column ~ binary column). In this example, we do the following:\n\nThe numeric columns of interest are selected from linelist - these become the .x inputs to map()\n\nThe function t.test() is supplied as the .f function, which is applied to each numeric column\n\nWithin the parentheses of t.test():\n\nthe first ~ precedes the .f that map() will iterate over .x\n\nthe .x represents the current column being supplied to the function t.test()\n\nthe second ~ is part of the t-test equation described above\n\nthe t.test() function expects a binary column on the right-hand side of the equation. We supply the vector linelist$gender independently and statically (note that it is not included in select()).\n\n\nmap() returns a list, so the output is a list of t-test results - one list element for each numeric column analysed.\n\n# Results are saved as a list\nt.test_results <- linelist %>% \n select(age, wt_kg, ht_cm, ct_blood, temp) %>% # keep only some numeric columns to map across\n map(.f = ~t.test(.x ~ linelist$gender)) # t.test function, with equation NUMERIC ~ CATEGORICAL\n\nHere is what the list t.test_results looks like when opened (Viewed) in RStudio. We have highlighted parts that are important for the examples in this page.\n\nYou can see at the top that the whole list is named t.test_results and has five elements. Those five elements are named age, wt_km, ht_cm, ct_blood, temp after each variable that was used in a t-test with gender from the linelist.\n\nEach of those five elements are themselves lists, with elements within them such as p.value and conf.int. Some of these elements like p.value are single numbers, whereas some such as estimate consist of two or more elements (mean in group f and mean in group m).\n\n\n\n\n\n\n\n\n\n\nNote: Remember that if you want to apply a function to only certain columns in a data frame, you can also simply use mutate() and across(), as explained in the Cleaning data and core functions page. Below is an example of applying as.character() to only the “age” columns. Note the placement of the parentheses and commas.\n\n# convert columns with column name containing \"age\" to class Character\nlinelist <- linelist %>% \n mutate(across(.cols = contains(\"age\"), .fns = as.character)) \n\n\n\nExtract from lists\nAs map() produces an output of class List, we will spend some time discussing how to extract data from lists using accompanying purrr functions. To demonstrate this, we will use the list t.test_results from the previous section. This is a list of 5 lists - each of the 5 lists contains the results of a t-test between a column from linelist data frame and its binary column gender. See the image in the section above for a visual of the list structure.\n\nNames of elements\nTo extract the names of the elements themselves, simply use names() from base R. In this case, we use names() on t.test_results to return the names of each sub-list, which are the names of the 5 variables that had t-tests performed.\n\nnames(t.test_results)\n\n[1] \"age\" \"wt_kg\" \"ht_cm\" \"ct_blood\" \"temp\" \n\n\n\n\nElements by name or position\nTo extract list elements by name or by position you can use brackets [[ ]] as described in the R basics page. Below we use double brackets to index the list t.tests_results and display the first element which is the results of the t-test on age.\n\nt.test_results[[1]] # first element by position\n\n\n Welch Two Sample t-test\n\ndata: .x by linelist$gender\nt = -21.3, df = 4902.9, p-value < 2.2e-16\nalternative hypothesis: true difference in means between group f and group m is not equal to 0\n95 percent confidence interval:\n -7.544409 -6.272675\nsample estimates:\nmean in group f mean in group m \n 12.66085 19.56939 \n\nt.test_results[[1]][\"p.value\"] # return element named \"p.value\" from first element \n\n$p.value\n[1] 2.350374e-96\n\n\nHowever, below we will demonstrate use of the simple and flexible purrr functions map() and pluck() to achieve the same outcomes.\n\n\npluck()\npluck() pulls out elements by name or by position. For example - to extract the t-test results for age, you can use pluck() like this:\n\nt.test_results %>% \n pluck(\"age\") # alternatively, use pluck(1)\n\n\n Welch Two Sample t-test\n\ndata: .x by linelist$gender\nt = -21.3, df = 4902.9, p-value < 2.2e-16\nalternative hypothesis: true difference in means between group f and group m is not equal to 0\n95 percent confidence interval:\n -7.544409 -6.272675\nsample estimates:\nmean in group f mean in group m \n 12.66085 19.56939 \n\n\nIndex deeper levels by specifying the further levels with commas. The below extracts the element named “p.value” from the list age within the list t.test_results. You can also use numbers instead of character names.\n\nt.test_results %>% \n pluck(\"age\", \"p.value\")\n\n[1] 2.350374e-96\n\n\nYou can extract such inner elements from all first-level elements by using map() to run the pluck() function across each first-level element. For example, the below code extracts the “p.value” elements from all lists within t.test_results. The list of t-test results is the .x iterated across, pluck() is the .f function being iterated, and the value “p-value” is provided to the function.\n\nt.test_results %>%\n map(pluck, \"p.value\") # return every p-value\n\n$age\n[1] 2.350374e-96\n\n$wt_kg\n[1] 2.664367e-182\n\n$ht_cm\n[1] 3.515713e-144\n\n$ct_blood\n[1] 0.4473498\n\n$temp\n[1] 0.5735923\n\n\nAs another alternative, map() offers a shorthand where you can write the element name in quotes, and it will pluck it out. If you use map() the output will be a list, whereas if you use map_chr() it will be a named character vector and if you use map_dbl() it will be a named numeric vector.\n\nt.test_results %>% \n map_dbl(\"p.value\") # return p-values as a named numeric vector\n\n age wt_kg ht_cm ct_blood temp \n 2.350374e-96 2.664367e-182 3.515713e-144 4.473498e-01 5.735923e-01 \n\n\nYou can read more about pluck() in it’s purrr documentation. It has a sibling function chuck() that will return an error instead of NULL if an element does not exist.\n\n\n\nConvert list to data frame\nThis is a complex topic - see the Resources section for more complete tutorials. Nevertheless, we will demonstrate converting the list of t-test results into a data frame. We will create a data frame with columns for the variable, its p-value, and the means from the two groups (male and female).\nHere are some of the new approaches and functions that will be used:\n\nThe function tibble() will be used to create a tibble (like a data frame)\n\nWe surround the tibble() function with curly brackets { } to prevent the entire t.test_results from being stored as the first tibble column\n\n\nWithin tibble(), each column is created explicitly, similar to the syntax of mutate():\n\nThe . represents t.test_results\nTo create a column with the t-test variable names (the names of each list element) we use names() as described above\n\nTo create a column with the p-values we use map_dbl() as described above to pull the p.value elements and convert them to a numeric vector\n\n\n\nt.test_results %>% {\n tibble(\n variables = names(.),\n p = map_dbl(., \"p.value\"))\n }\n\n# A tibble: 5 × 2\n variables p\n <chr> <dbl>\n1 age 2.35e- 96\n2 wt_kg 2.66e-182\n3 ht_cm 3.52e-144\n4 ct_blood 4.47e- 1\n5 temp 5.74e- 1\n\n\nBut now let’s add columns containing the means for each group (males and females).\nWe would need to extract the element estimate, but this actually contains two elements within it (mean in group f and mean in group m). So, it cannot be simplified into a vector with map_chr() or map_dbl(). Instead, we use map(), which used within tibble() will create a column of class list within the tibble! Yes, this is possible!\n\nt.test_results %>% \n {tibble(\n variables = names(.),\n p = map_dbl(., \"p.value\"),\n means = map(., \"estimate\"))}\n\n# A tibble: 5 × 3\n variables p means \n <chr> <dbl> <named list>\n1 age 2.35e- 96 <dbl [2]> \n2 wt_kg 2.66e-182 <dbl [2]> \n3 ht_cm 3.52e-144 <dbl [2]> \n4 ct_blood 4.47e- 1 <dbl [2]> \n5 temp 5.74e- 1 <dbl [2]> \n\n\nOnce you have this list column, there are several tidyr functions (part of tidyverse) that help you “rectangle” or “un-nest” these “nested list” columns. Read more about them here, or by running vignette(\"rectangle\"). In brief:\n\nunnest_wider() - gives each element of a list-column its own column\n\nunnest_longer() - gives each element of a list-column its own row\nhoist() - acts like unnest_wider() but you specify which elements to unnest\n\nBelow, we pass the tibble to unnest_wider() specifying the tibble’s means column (which is a nested list). The result is that means is replaced by two new columns, each reflecting the two elements that were previously in each means cell.\n\nt.test_results %>% \n {tibble(\n variables = names(.),\n p = map_dbl(., \"p.value\"),\n means = map(., \"estimate\")\n )} %>% \n unnest_wider(means)\n\n# A tibble: 5 × 4\n variables p `mean in group f` `mean in group m`\n <chr> <dbl> <dbl> <dbl>\n1 age 2.35e- 96 12.7 19.6\n2 wt_kg 2.66e-182 45.8 59.6\n3 ht_cm 3.52e-144 109. 142. \n4 ct_blood 4.47e- 1 21.2 21.2\n5 temp 5.74e- 1 38.6 38.6\n\n\n\n\nDiscard, keep, and compact lists\nBecause working with purrr so often involves lists, we will briefly explore some purrr functions to modify lists. See the Resources section for more complete tutorials on purrr functions.\n\nlist_modify() has many uses, one of which can be to remove a list element\n\nkeep() retains the elements specified to .p =, or where a function supplied to .p = evaluates to TRUE\n\ndiscard() removes the elements specified to .p, or where a function supplied to .p = evaluates to TRUE\n\ncompact() removes all empty elements\n\nHere are some examples using the combined list created in the section above on using map() to import and combine multiple files (it contains 6 case linelist data frames):\nElements can be removed by name with list_modify() and setting the name equal to NULL.\n\ncombined %>% \n list_modify(\"Central Hospital\" = NULL) # remove list element by name\n\nYou can also remove elements by criteria, by providing a “predicate” equation to .p = (an equation that evaluates to either TRUE or FALSE). Place a tilde ~ before the function and use .x to represent the list element. Using keep() the list elements that evaluate to TRUE will be kept. Inversely, if using discard() the list elements that evaluate to TRUE will be removed.\n\n# keep only list elements with more than 500 rows\ncombined %>% \n keep(.p = ~nrow(.x) > 500) \n\nIn the below example, list elements are discarded if their class are not data frames.\n\n# Discard list elements that are not data frames\ncombined %>% \n discard(.p = ~class(.x) != \"data.frame\")\n\nYour predicate function can also reference elements/columns within each list item. For example, below, list elements where the mean of column ct_blood is over 25 are discarded.\n\n# keep only list elements where ct_blood column mean is over 25\ncombined %>% \n discard(.p = ~mean(.x$ct_blood) > 25) \n\nThis command would remove all empty list elements:\n\n# Remove all empty list elements\ncombined %>% \n compact()\n\n\n\npmap()\nTHIS SECTION IS UNDER CONSTRUCTION", + "text": "16.3 purrr and lists\nAnother approach to iterative operations is the purrr package - it is the tidyverse approach to iteration.\nIf you are faced with performing the same task several times, it is probably worth creating a generalised solution that you can use across many inputs. For example, producing plots for multiple jurisdictions, or importing and combining many files.\nThere are also a few other advantages to purrr - you can use it with pipes %>%, it handles errors better than normal for loops, and the syntax is quite clean and simple! If you are using a for loop, you can probably do it more clearly and succinctly with purrr!\nKeep in mind that purrr is a functional programming tool. That is, the operations that are to be iteratively applied are wrapped up into functions. See the Writing functions page to learn how to write your own functions.\npurrr is also almost entirely based around lists and vectors - so think about it as applying a function to each element of that list/vector!\n\nLoad packages\npurrr is part of the tidyverse, so there is no need to install/load a separate package.\n\npacman::p_load(\n rio, # import/export\n here, # relative filepaths\n tidyverse, # data mgmt and viz\n writexl, # write Excel file with multiple sheets\n readxl # import Excel with multiple sheets\n)\n\n\n\nmap()\nOne core purrr function is map(), which “maps” (applies) a function to each input element of a list/vector you provide.\nThe basic syntax is map(.x = SEQUENCE, .f = FUNCTION, OTHER ARGUMENTS). In a bit more detail:\n\n.x = are the inputs upon which the .f function will be iteratively applied - e.g. a vector of jurisdiction names, columns in a data frame, or a list of data frames.\n\n.f = is the function to apply to each element of the .x input - it could be a function like print() that already exists, or a custom function that you define. The function is often written after a tilde ~ (details below).\n\nA few more notes on syntax:\n\nIf the function needs no further arguments specified, it can be written with no parentheses and no tilde (e.g. .f = mean). To provide arguments that will be the same value for each iteration, provide them within map() but outside the .f = argument, such as the na.rm = T in map(.x = my_list, .f = mean, na.rm=T).\n\nYou can use .x (or simply .) within the .f = function as a placeholder for the .x value of that iteration\n\nUse tilde syntax (~) to have greater control over the function - write the function as normal with parentheses, such as: map(.x = my_list, .f = ~mean(., na.rm = T)). Use this syntax particularly if the value of an argument will change each iteration, or if it is the value .x itself (see examples below)\n\nThe output of using map() is a list - a list is an object class like a vector but whose elements can be of different classes. So, a list produced by map() could contain many data frames, or many vectors, many single values, or even many lists! There are alternative versions of map() explained below that produce other types of outputs (e.g. map_dfr() to produce a data frame, map_chr() to produce character vectors, and map_dbl() to produce numeric vectors).\n\nExample - import and combine Excel sheets\nLet’s demonstrate with a common epidemiologist task: - You want to import an Excel workbook with case data, but the data are split across different named sheets in the workbook. How do you efficiently import and combine the sheets into one data frame?\nLet’s say we are sent the below Excel workbook. Each sheet contains cases from a given hospital.\n\n\n\n\n\n\n\n\n\nHere is one approach that uses map():\n\nmap() the function import() so that it runs for each Excel sheet.\nCombine the imported data frames into one using bind_rows().\n\nAlong the way, preserve the original sheet name for each row, storing this information in a new column in the final data frame.\n\nFirst, we need to extract the sheet names and save them. We provide the Excel workbook’s file path to the function excel_sheets() from the package readxl, which extracts the sheet names. We store them in a character vector called sheet_names.\n\nsheet_names <- readxl::excel_sheets(\"hospital_linelists.xlsx\")\n\nHere are the names:\n\nsheet_names\n\n[1] \"Central Hospital\" \"Military Hospital\" \n[3] \"Missing\" \"Other\" \n[5] \"Port Hospital\" \"St. Mark's Maternity Hospital\"\n\n\nNow that we have this vector of names, map() can provide them one-by-one to the function import(). In this example, the sheet_names are .x and import() is the function .f.\nRecall from the Import and export page that when used on Excel workbooks, import() can accept the argument which = specifying the sheet to import. Within the .f function import(), we provide which = .x, whose value will change with each iteration through the vector sheet_names - first “Central Hospital”, then “Military Hospital”, etc.\nOf note - because we have used map(), the data in each Excel sheet will be saved as a separate data frame within a list. We want each of these list elements (data frames) to have a name, so before we pass sheet_names to map() we pass it through set_names() from purrr, which ensures that each list element gets the appropriate name.\nWe save the output list as combined.\n\ncombined <- sheet_names %>% \n purrr::set_names() %>% \n map(.f = ~import(\"hospital_linelists.xlsx\", which = .x))\n\nWhen we inspect output, we see that the data from each Excel sheet is saved in the list with a name. This is good, but we are not quite finished.\n\n\n\n\n\n\n\n\n\nLastly, we use the function bind_rows() (from dplyr) which accepts the list of similarly-structured data frames and combines them into one data frame. To create a new column from the list element names, we use the argument .id = and provide it with the desired name for the new column.\nBelow is the whole sequence of commands:\n\nsheet_names <- readxl::excel_sheets(\"hospital_linelists.xlsx\") # extract sheet names\n \ncombined <- sheet_names %>% # begin with sheet names\n purrr::set_names() %>% # set their names\n map(.f = ~import(\"hospital_linelists.xlsx\", which = .x)) %>% # iterate, import, save in list\n bind_rows(.id = \"origin_sheet\") # combine list of data frames, preserving origin in new column \n\nAnd now we have one data frame with a column containing the sheet of origin!\n\n\n\n\n\n\n\n\n\nThere are variations of map() that you should be aware of. For example, map_dfr() returns a data frame, not a list. Thus, we could have used it for the task above and not have had to bind rows. But then we would not have been able to capture which sheet (hospital) each case came from.\nOther variations include map_chr(), map_dbl(). These are very useful functions for two reasons. Firstly. they automatically convert the output of an iterative function into a vector (not a list). Secondly, they can explicitly control the class that the data comes back in - you ensure that your data comes back as a character vector with map_chr(), or numeric vector with map_dbl(). Lets return to these later in the section!\nThe functions map_at() and map_if() are also very useful for iteration - they allow you to specify which elements of a list you should iterate at! These work by simply applying a vector of indexes/names (in the case of map_at()) or a logical test (in the case of map_if()).\nLets use an example where we didn’t want to read the first sheet of hospital data. We use map_at() instead of map(), and specify the .at = argument to c(-1) which means to not use the first element of .x. Alternatively, you can provide a vector of positive numbers, or names, to .at = to specify which elements to use.\n\nsheet_names <- readxl::excel_sheets(\"hospital_linelists.xlsx\")\n\ncombined <- sheet_names %>% \n purrr::set_names() %>% \n # exclude the first sheet\n map_at(.f = ~import( \"hospital_linelists.xlsx\", which = .x),\n .at = c(-1))\n\nNote that the first sheet name will still appear as an element of the output list - but it is only a single character name (not a data frame). You would need to remove this element before binding rows. We will cover how to remove and modify list elements in a later section.\n\n\n\nSplit dataset and export\nBelow, we give an example of how to split a dataset into parts and then use map() iteration to export each part as a separate Excel sheet, or as a separate CSV file.\n\nSplit dataset\nLet’s say we have the complete case linelist as a data frame, and we now want to create a separate linelist for each hospital and export each as a separate CSV file. Below, we do the following steps:\nUse group_split() (from dplyr) to split the linelist data frame by unique values in column hospital. The output is a list containing one data frame per hospital subset.\n\nlinelist_split <- linelist %>% \n group_split(hospital)\n\nWe can run View(linelist_split) and see that this list contains 6 data frames (“tibbles”), each representing the cases from one hospital.\n\n\n\n\n\n\n\n\n\nHowever, note that the data frames in the list do not have names by default! We want each to have a name, and then to use that name when saving the CSV file.\nOne approach to extracting the names is to use pull() (from dplyr) to extract the hospital column from each data frame in the list. Then, to be safe, we convert the values to character and then use unique() to get the name for that particular data frame. All of these steps are applied to each data frame via map().\n\nnames(linelist_split) <- linelist_split %>% # Assign to names of listed data frames \n # Extract the names by doing the following to each data frame: \n map(.f = ~pull(.x, hospital)) %>% # Pull out hospital column\n map(.f = ~as.character(.x)) %>% # Convert to character, just in case\n map(.f = ~unique(.x)) # Take the unique hospital name\n\nWe can now see that each of the list elements has a name. These names can be accessed via names(linelist_split).\n\n\n\n\n\n\n\n\n\n\nnames(linelist_split)\n\n[1] \"Central Hospital\" \n[2] \"Military Hospital\" \n[3] \"Missing\" \n[4] \"Other\" \n[5] \"Port Hospital\" \n[6] \"St. Mark's Maternity Hospital (SMMH)\"\n\n\n\nMore than one group_split() column\nIf you wanted to split the linelist by more than one grouping column, such as to produce subset linelist by intersection of hospital AND gender, you will need a different approach to naming the list elements. This involves collecting the unique “group keys” using group_keys() from dplyr - they are returned as a data frame. Then you can combine the group keys into values with unite() as shown below, and assign these conglomerate names to linelist_split.\n\n# split linelist by unique hospital-gender combinations\nlinelist_split <- linelist %>% \n group_split(hospital, gender)\n\n# extract group_keys() as a dataframe\ngroupings <- linelist %>% \n group_by(hospital, gender) %>% \n group_keys()\n\ngroupings # show unique groupings \n\n# A tibble: 18 × 2\n hospital gender\n <chr> <chr> \n 1 Central Hospital f \n 2 Central Hospital m \n 3 Central Hospital <NA> \n 4 Military Hospital f \n 5 Military Hospital m \n 6 Military Hospital <NA> \n 7 Missing f \n 8 Missing m \n 9 Missing <NA> \n10 Other f \n11 Other m \n12 Other <NA> \n13 Port Hospital f \n14 Port Hospital m \n15 Port Hospital <NA> \n16 St. Mark's Maternity Hospital (SMMH) f \n17 St. Mark's Maternity Hospital (SMMH) m \n18 St. Mark's Maternity Hospital (SMMH) <NA> \n\n\nNow we combine the groupings together, separated by dashes, and assign them as the names of list elements in linelist_split. This takes some extra lines as we replace NA with “Missing”, use unite() from dplyr to combine the column values together (separated by dashes), and then convert into an un-named vector so it can be used as names of linelist_split.\n\n# Combine into one name value \nnames(linelist_split) <- groupings %>% \n mutate(across(everything(), replace_na, \"Missing\")) %>% # replace NA with \"Missing\" in all columns\n unite(\"combined\", sep = \"-\") %>% # Unite all column values into one\n setNames(NULL) %>% \n as_vector() %>% \n as.list()\n\n\n\n\nExport as Excel sheets\nTo export the hospital linelists as an Excel workbook with one linelist per sheet, we can just provide the named list linelist_split to the write_xlsx() function from the writexl package. This has the ability to save one Excel workbook with multiple sheets. The list element names are automatically applied as the sheet names.\n\nlinelist_split %>% \n writexl::write_xlsx(path = here(\"data\", \"hospital_linelists.xlsx\"))\n\nYou can now open the Excel file and see that each hospital has its own sheet.\n\n\n\n\n\n\n\n\n\n\n\nExport as CSV files\nIt is a bit more complex command, but you can also export each hospital-specific linelist as a separate CSV file, with a file name specific to the hospital.\nAgain we use map(): we take the vector of list element names (shown above) and use map() to iterate through them, applying export() (from the rio package, see Import and export page) on the data frame in the list linelist_split that has that name. We also use the name to create a unique file name. Here is how it works:\n\nWe begin with the vector of character names, passed to map() as .x.\n\nThe .f function is export() , which requires a data frame and a file path to write to.\n\nThe input .x (the hospital name) is used within .f to extract/index that specific element of linelist_split list. This results in only one data frame at a time being provided to export().\n\nFor example, when map() iterates for “Military Hospital”, then linelist_split[[.x]] is actually linelist_split[[\"Military Hospital\"]], thus returning the second element of linelist_split - which is all the cases from Military Hospital.\n\nThe file path provided to export() is dynamic via use of str_glue() (see Characters and strings page):\n\nhere() is used to get the base of the file path and specify the “data” folder (note single quotes to not interrupt the str_glue() double quotes).\n\n\nThen a slash /, and then again the .x which prints the current hospital name to make the file identifiable.\n\nFinally the extension “.csv” which export() uses to create a CSV file.\n\n\nnames(linelist_split) %>%\n map(.f = ~export(linelist_split[[.x]], file = str_glue(\"{here('data')}/{.x}.csv\")))\n\nNow you can see that each file is saved in the “data” folder of the R Project “Epi_R_handbook”!\n\n\n\n\n\n\n\n\n\n\n\n\nCustom functions\nYou may want to create your own function to provide to map().\nLet’s say we want to create epidemic curves for each hospital’s cases. To do this using purrr, our .f function can be ggplot() and extensions with + as usual. As the output of map() is always a list, the plots are stored in a list. Because they are plots, they can be extracted and plotted with the ggarrange() function from the ggpubr package (documentation).\n\n# load package for plotting elements from list\npacman::p_load(ggpubr)\n\n# map across the vector of 6 hospital \"names\" (created earlier)\n# use the ggplot function specified\n# output is a list with 6 ggplots\n\nhospital_names <- unique(linelist$hospital)\n\nmy_plots <- map(\n .x = hospital_names,\n .f = ~ggplot(data = linelist %>% filter(hospital == .x)) +\n geom_histogram(aes(x = date_onset)) +\n labs(title = .x)\n)\n\n# print the ggplots (they are stored in a list)\nggarrange(plotlist = my_plots, ncol = 2, nrow = 3)\n\n\n\n\n\n\n\n\nYou can also use map() with ggsave() to loop through and save plots.\n\nmap(\n .x = hospital_names,\n .f = ~ggsave(\n filename = here::here(\n str_glue(\"epicurve_{.x}.png\")),\n ggplot(data = linelist %>% \n filter(hospital == .x)) +\n geom_histogram(aes(x = date_onset)) +\n labs(title = .x)\n )\n)\n\nIf this map() code looks too messy, you can achieve the same result by saving your specific ggplot() command as a custom user-defined function, for example we can name it make_epicurve()). This function is then used within the map(). .x will be iteratively replaced by the hospital name, and used as hosp_name in the make_epicurve() function. See the page on Writing functions.\n\n# Create function\nmake_epicurve <- function(hosp_name){\n \n ggplot(data = linelist %>% filter(hospital == hosp_name)) +\n geom_histogram(aes(x = date_onset)) +\n theme_classic()+\n labs(title = hosp_name)\n \n}\n\n\n# mapping\nmy_plots <- map(hospital_names, ~make_epicurve(hosp_name = .x))\n\n# print the ggplots (they are stored in a list)\nggarrange(plotlist = my_plots, ncol = 2, nrow = 3)\n\n\n\nMapping a function across columns\nAnother common use-case is to map a function across many columns. Below, we map() the function t.test() across numeric columns in the data frame linelist, comparing the numeric values by gender.\nRecall from the page on Simple statistical tests that t.test() can take inputs in a formula format, such as t.test(numeric column ~ binary column). In this example, we do the following:\n\nThe numeric columns of interest are selected from linelist - these become the .x inputs to map().\n\nThe function t.test() is supplied as the .f function, which is applied to each numeric column.\n\nWithin the parentheses of t.test():\n\nthe first ~ precedes the .f that map() will iterate over .x.\n\nthe .x represents the current column being supplied to the function t.test().\n\nthe second ~ is part of the t-test equation described above.\n\nthe t.test() function expects a binary column on the right-hand side of the equation. We supply the vector linelist$gender independently and statically (note that it is not included in select()).\n\n\nmap() returns a list, so the output is a list of t-test results - one list element for each numeric column analysed.\n\n# Results are saved as a list\nt.test_results <- linelist %>% \n select(age, wt_kg, ht_cm, ct_blood, temp) %>% # keep only some numeric columns to map across\n map(.f = ~t.test(.x ~ linelist$gender)) # t.test function, with equation NUMERIC ~ CATEGORICAL\n\nHere is what the list t.test_results looks like when opened (Viewed) in RStudio. We have highlighted parts that are important for the examples in this page.\n\nYou can see at the top that the whole list is named t.test_results and has five elements. Those five elements are named age, wt_km, ht_cm, ct_blood, temp after each variable that was used in a t-test with gender from the linelist.\n\nEach of those five elements are themselves lists, with elements within them such as p.value and conf.int. Some of these elements like p.value are single numbers, whereas some such as estimate consist of two or more elements (mean in group f and mean in group m).\n\n\n\n\n\n\n\n\n\n\nNote: Remember that if you want to apply a function to only certain columns in a data frame, you can also simply use mutate() and across(), as explained in the Cleaning data and core functions page. Below is an example of applying as.character() to only the “age” columns. Note the placement of the parentheses and commas.\n\n# convert columns with column name containing \"age\" to class Character\nlinelist <- linelist %>% \n mutate(across(.cols = contains(\"age\"), .fns = as.character)) \n\n\n\nExtract from lists\nAs map() produces an output of class List, we will spend some time discussing how to extract data from lists using accompanying purrr functions. To demonstrate this, we will use the list t.test_results from the previous section. This is a list of 5 lists - each of the 5 lists contains the results of a t-test between a column from linelist data frame and its binary column gender. See the image in the section above for a visual of the list structure.\n\nNames of elements\nTo extract the names of the elements themselves, simply use names() from base R. In this case, we use names() on t.test_results to return the names of each sub-list, which are the names of the 5 variables that had t-tests performed.\n\nnames(t.test_results)\n\n[1] \"age\" \"wt_kg\" \"ht_cm\" \"ct_blood\" \"temp\" \n\n\n\n\nElements by name or position\nTo extract list elements by name or by position you can use brackets [[ ]] as described in the R basics page. Below we use double brackets to index the list t.tests_results and display the first element which is the results of the t-test on age.\n\nt.test_results[[1]] # first element by position\n\n\n Welch Two Sample t-test\n\ndata: .x by linelist$gender\nt = -21.3, df = 4902.9, p-value < 2.2e-16\nalternative hypothesis: true difference in means between group f and group m is not equal to 0\n95 percent confidence interval:\n -7.544409 -6.272675\nsample estimates:\nmean in group f mean in group m \n 12.66085 19.56939 \n\nt.test_results[[1]][\"p.value\"] # return element named \"p.value\" from first element \n\n$p.value\n[1] 2.350374e-96\n\n\nHowever, below we will demonstrate use of the simple and flexible purrr functions map() and pluck() to achieve the same outcomes.\n\n\npluck()\npluck() pulls out elements by name or by position. For example - to extract the t-test results for age, you can use pluck() like this:\n\nt.test_results %>% \n pluck(\"age\") # alternatively, use pluck(1)\n\n\n Welch Two Sample t-test\n\ndata: .x by linelist$gender\nt = -21.3, df = 4902.9, p-value < 2.2e-16\nalternative hypothesis: true difference in means between group f and group m is not equal to 0\n95 percent confidence interval:\n -7.544409 -6.272675\nsample estimates:\nmean in group f mean in group m \n 12.66085 19.56939 \n\n\nIndex deeper levels by specifying the further levels with commas. The below extracts the element named “p.value” from the list age within the list t.test_results. You can also use numbers instead of character names.\n\nt.test_results %>% \n pluck(\"age\", \"p.value\")\n\n[1] 2.350374e-96\n\n\nYou can extract such inner elements from all first-level elements by using map() to run the pluck() function across each first-level element. For example, the below code extracts the “p.value” elements from all lists within t.test_results. The list of t-test results is the .x iterated across, pluck() is the .f function being iterated, and the value “p-value” is provided to the function.\n\nt.test_results %>%\n map(pluck, \"p.value\") # return every p-value\n\n$age\n[1] 2.350374e-96\n\n$wt_kg\n[1] 2.664367e-182\n\n$ht_cm\n[1] 3.515713e-144\n\n$ct_blood\n[1] 0.4473498\n\n$temp\n[1] 0.5735923\n\n\nAs another alternative, map() offers a shorthand where you can write the element name in quotes, and it will pluck it out. If you use map() the output will be a list, whereas if you use map_chr() it will be a named character vector and if you use map_dbl() it will be a named numeric vector.\n\nt.test_results %>% \n map_dbl(\"p.value\") # return p-values as a named numeric vector\n\n age wt_kg ht_cm ct_blood temp \n 2.350374e-96 2.664367e-182 3.515713e-144 4.473498e-01 5.735923e-01 \n\n\nYou can read more about pluck() in it’s purrr documentation. It has a sibling function chuck() that will return an error instead of NULL if an element does not exist.\n\n\n\nConvert list to data frame\nThis is a complex topic - see the Resources section for more complete tutorials. Nevertheless, we will demonstrate converting the list of t-test results into a data frame. We will create a data frame with columns for the variable, its p-value, and the means from the two groups (male and female).\nHere are some of the new approaches and functions that will be used:\n\nThe function tibble() will be used to create a tibble (like a data frame).\n\nWe surround the tibble() function with curly brackets { } to prevent the entire t.test_results from being stored as the first tibble column.\n\n\nWithin tibble(), each column is created explicitly, similar to the syntax of mutate():\n\nThe . represents t.test_results.\nTo create a column with the t-test variable names (the names of each list element) we use names() as described above.\n\nTo create a column with the p-values we use map_dbl() as described above to pull the p.value elements and convert them to a numeric vector.\n\n\n\nt.test_results %>% {\n tibble(\n variables = names(.),\n p = map_dbl(., \"p.value\"))\n }\n\n# A tibble: 5 × 2\n variables p\n <chr> <dbl>\n1 age 2.35e- 96\n2 wt_kg 2.66e-182\n3 ht_cm 3.52e-144\n4 ct_blood 4.47e- 1\n5 temp 5.74e- 1\n\n\nBut now let’s add columns containing the means for each group (males and females).\nWe would need to extract the element estimate, but this actually contains two elements within it (mean in group f and mean in group m). So, it cannot be simplified into a vector with map_chr() or map_dbl(). Instead, we use map(), which used within tibble() will create a column of class list within the tibble! Yes, this is possible!\n\nt.test_results %>% \n {tibble(\n variables = names(.),\n p = map_dbl(., \"p.value\"),\n means = map(., \"estimate\"))}\n\n# A tibble: 5 × 3\n variables p means \n <chr> <dbl> <named list>\n1 age 2.35e- 96 <dbl [2]> \n2 wt_kg 2.66e-182 <dbl [2]> \n3 ht_cm 3.52e-144 <dbl [2]> \n4 ct_blood 4.47e- 1 <dbl [2]> \n5 temp 5.74e- 1 <dbl [2]> \n\n\nOnce you have this list column, there are several tidyr functions (part of tidyverse) that help you “rectangle” or “un-nest” these “nested list” columns. Read more about them here, or by running vignette(\"rectangle\"). In brief:\n\nunnest_wider() - gives each element of a list-column its own column.\n\nunnest_longer() - gives each element of a list-column its own row.\nhoist() - acts like unnest_wider() but you specify which elements to unnest.\n\nBelow, we pass the tibble to unnest_wider() specifying the tibble’s means column (which is a nested list). The result is that means is replaced by two new columns, each reflecting the two elements that were previously in each means cell.\n\nt.test_results %>% \n {tibble(\n variables = names(.),\n p = map_dbl(., \"p.value\"),\n means = map(., \"estimate\")\n )} %>% \n unnest_wider(means)\n\n# A tibble: 5 × 4\n variables p `mean in group f` `mean in group m`\n <chr> <dbl> <dbl> <dbl>\n1 age 2.35e- 96 12.7 19.6\n2 wt_kg 2.66e-182 45.8 59.6\n3 ht_cm 3.52e-144 109. 142. \n4 ct_blood 4.47e- 1 21.2 21.2\n5 temp 5.74e- 1 38.6 38.6\n\n\n\n\nDiscard, keep, and compact lists\nBecause working with purrr so often involves lists, we will briefly explore some purrr functions to modify lists. See the Resources section for more complete tutorials on purrr functions.\n\nlist_modify() has many uses, one of which can be to remove a list element.\n\nkeep() retains the elements specified to .p =, or where a function supplied to .p = evaluates to TRUE.\n\ndiscard() removes the elements specified to .p, or where a function supplied to .p = evaluates to TRUE.\n\ncompact() removes all empty elements.\n\nHere are some examples using the combined list created in the section above on using map() to import and combine multiple files (it contains 6 case linelist data frames):\nElements can be removed by name with list_modify() and setting the name equal to NULL.\n\ncombined %>% \n list_modify(\"Central Hospital\" = NULL) # remove list element by name\n\nYou can also remove elements by criteria, by providing a “predicate” equation to .p = (an equation that evaluates to either TRUE or FALSE). Place a tilde ~ before the function and use .x to represent the list element. Using keep() the list elements that evaluate to TRUE will be kept. Inversely, if using discard() the list elements that evaluate to TRUE will be removed.\n\n# keep only list elements with more than 500 rows\ncombined %>% \n keep(.p = ~nrow(.x) > 500) \n\nIn the below example, list elements are discarded if their class are not data frames.\n\n# Discard list elements that are not data frames\ncombined %>% \n discard(.p = ~class(.x) != \"data.frame\")\n\nYour predicate function can also reference elements/columns within each list item. For example, below, list elements where the mean of column ct_blood is over 25 are discarded.\n\n# keep only list elements where ct_blood column mean is over 25\ncombined %>% \n discard(.p = ~mean(.x$ct_blood) > 25) \n\nThis command would remove all empty list elements:\n\n# Remove all empty list elements\ncombined %>% \n compact()\n\n\n\npmap()\nThe function pmap() from the purrr package allows us to apply map_*() functions over multiple vectors. The “p” in pmap() stands for parallel. It works down a a dataset or list sequentially, carrying out your operation. Note, it does not refer to parallel computing.\nIn pmap() you specify a single dataset, or list that contains all of the vectors, or lists, that you want to supply your function. This can allow you to very quickly carry out calculations with multiple columns of a dataframe, or lists of information.\nFor example, here is a simple dataset of three numbers.\n\ndata_generic <- data.frame(\n A = c(1, 10, 100),\n B = c(3, 6, 9),\n C = c(25, 75, 50)\n)\n\ndata_generic\n\n A B C\n1 1 3 25\n2 10 6 75\n3 100 9 50\n\n\nHere we are going to using the function sum() from base R to look at what the sum of each row is.\n\ndata_generic %>% #Our dataset\n pmap_dbl(sum) #The function we want to use\n\n[1] 29 91 159\n\n\nYou can see that the function pmap_dbl has gone through each row of the datasets, and summed the values. While there are other ways of carrying out this operation in this example, such as using rowSums() from base, pmap_*() functions are much quicker. Additionally, pmap_*() allows you to input custom functions, and specify more complicated inputs.\nFor example, here we are going to create a new column to count how many symptoms those in our linelist dataset have.\n\nlinelist_symptom_count <- linelist %>% #Our dataset\n mutate(number_symptoms = linelist %>% #Creating a new column to count symptoms\n select(fever:vomit) %>% #Selecting the columns that indicate the presence of symptoms\n pmap_int(~sum(c(...) == \"yes\", na.rm = T))) #Here pmap is looking at each row of symptoms, counting which values are set as \"yes\" and then summing all the values in the row\n\n#Display the results\nlinelist_symptom_count %>%\n select(fever:vomit, number_symptoms) %>%\n slice(1:10)\n\n fever chills cough aches vomit number_symptoms\n1 no no yes no yes 2\n2 <NA> <NA> <NA> <NA> <NA> 0\n3 <NA> <NA> <NA> <NA> <NA> 0\n4 no no no no no 0\n5 no no yes no yes 2\n6 no no yes no yes 2\n7 <NA> <NA> <NA> <NA> <NA> 0\n8 no no yes no yes 2\n9 no no yes no yes 2\n10 no no yes no no 1\n\n\nAs another example, here we have written our own custom function, using str_glue, see section on Characters and strings to summarise each patient’s gender, age, date of onset, outcome and the date of outcome:\n\n#Function\nsummarise_function <- function(case_id, gender, age, date_onset, date_outcome, outcome, ...){\n str_glue(\"Case {case_id} who had the gender {gender} and the age {age}, had symptom onset on {date_onset}, and had the outcome of {outcome} on {date_outcome}.\")\n}\n\n#Run the custom pmap function\nlinelist_summary <- linelist %>%\n pmap_chr(summarise_function)\n\n#Display only the first 2 for ease of viewing\nlinelist_summary[1:2]\n\n[1] \"Case 5fe599 who had the gender m and the age 2, had symptom onset on 2014-05-13, and had the outcome of NA on NA.\" \n[2] \"Case 8689b7 who had the gender f and the age 3, had symptom onset on 2014-05-13, and had the outcome of Recover on 2014-05-18.\"\n\n\nNote that here we did not even have to specify which columns to use, as they are the same name in the function, summarise_function() as in the dataset. pmap_*() functions automatically map the column or list names to the function.", "crumbs": [ "Data Management", "16  Iteration, loops, and lists" @@ -1550,7 +1550,7 @@ "href": "new_pages/tables_descriptive.html#tbl_janitor", "title": "17  Descriptive tables", "section": "17.3 janitor package", - "text": "17.3 janitor package\nThe janitor packages offers the tabyl() function to produce tabulations and cross-tabulations, which can be “adorned” or modified with helper functions to display percents, proportions, counts, etc.\nBelow, we pipe the linelist data frame to janitor functions and print the result. If desired, you can also save the resulting tables with the assignment operator <-.\n\nSimple tabyl\nThe default use of tabyl() on a specific column produces the unique values, counts, and column-wise “percents” (actually proportions). The proportions may have many digits. You can adjust the number of decimals with adorn_rounding() as described below.\n\nlinelist %>% tabyl(age_cat)\n\n age_cat n percent valid_percent\n 0-4 1095 0.185971467 0.188728025\n 5-9 1095 0.185971467 0.188728025\n 10-14 941 0.159816576 0.162185453\n 15-19 743 0.126188859 0.128059290\n 20-29 1073 0.182235054 0.184936229\n 30-49 754 0.128057065 0.129955188\n 50-69 95 0.016134511 0.016373664\n 70+ 6 0.001019022 0.001034126\n <NA> 86 0.014605978 NA\n\n\nAs you can see above, if there are missing values they display in a row labeled <NA>. You can suppress them with show_na = FALSE. If there are no missing values, this row will not appear. If there are missing values, all proportions are given as both raw (denominator inclusive of NA counts) and “valid” (denominator excludes NA counts).\nIf the column is class Factor and only certain levels are present in your data, all levels will still appear in the table. You can suppress this feature by specifying show_missing_levels = FALSE. Read more on the Factors page.\n\n\nCross-tabulation\nCross-tabulation counts are achieved by adding one or more additional columns within tabyl(). Note that now only counts are returned - proportions and percents can be added with additional steps shown below.\n\nlinelist %>% tabyl(age_cat, gender)\n\n age_cat f m NA_\n 0-4 640 416 39\n 5-9 641 412 42\n 10-14 518 383 40\n 15-19 359 364 20\n 20-29 468 575 30\n 30-49 179 557 18\n 50-69 2 91 2\n 70+ 0 5 1\n <NA> 0 0 86\n\n\n\n\n“Adorning” the tabyl\nUse janitor’s “adorn” functions to add totals or convert to proportions, percents, or otherwise adjust the display. Often, you will pipe the tabyl through several of these functions.\n\n\n\n\n\n\n\nFunction\nOutcome\n\n\n\n\nadorn_totals()\nAdds totals (where = “row”, “col”, or “both”). Set name = for “Total”.\n\n\nadorn_percentages()\nConvert counts to proportions, with denominator = “row”, “col”, or “all”\n\n\nadorn_pct_formatting()\nConverts proportions to percents. Specify digits =. Remove the “%” symbol with affix_sign = FALSE.\n\n\nadorn_rounding()\nTo round proportions to digits = places. To round percents use adorn_pct_formatting() with digits =.\n\n\nadorn_ns()\nAdd counts to a table of proportions or percents. Indicate position = “rear” to show counts in parentheses, or “front” to put the percents in parentheses.\n\n\nadorn_title()\nAdd string via arguments row_name = and/or col_name =\n\n\n\nBe conscious of the order you apply the above functions. Below are some examples.\nA simple one-way table with percents instead of the default proportions.\n\nlinelist %>% # case linelist\n tabyl(age_cat) %>% # tabulate counts and proportions by age category\n adorn_pct_formatting() # convert proportions to percents\n\n age_cat n percent valid_percent\n 0-4 1095 18.6% 18.9%\n 5-9 1095 18.6% 18.9%\n 10-14 941 16.0% 16.2%\n 15-19 743 12.6% 12.8%\n 20-29 1073 18.2% 18.5%\n 30-49 754 12.8% 13.0%\n 50-69 95 1.6% 1.6%\n 70+ 6 0.1% 0.1%\n <NA> 86 1.5% -\n\n\nA cross-tabulation with a total row and row percents.\n\nlinelist %>% \n tabyl(age_cat, gender) %>% # counts by age and gender\n adorn_totals(where = \"row\") %>% # add total row\n adorn_percentages(denominator = \"row\") %>% # convert counts to proportions\n adorn_pct_formatting(digits = 1) # convert proportions to percents\n\n age_cat f m NA_\n 0-4 58.4% 38.0% 3.6%\n 5-9 58.5% 37.6% 3.8%\n 10-14 55.0% 40.7% 4.3%\n 15-19 48.3% 49.0% 2.7%\n 20-29 43.6% 53.6% 2.8%\n 30-49 23.7% 73.9% 2.4%\n 50-69 2.1% 95.8% 2.1%\n 70+ 0.0% 83.3% 16.7%\n <NA> 0.0% 0.0% 100.0%\n Total 47.7% 47.6% 4.7%\n\n\nA cross-tabulation adjusted so that both counts and percents are displayed.\n\nlinelist %>% # case linelist\n tabyl(age_cat, gender) %>% # cross-tabulate counts\n adorn_totals(where = \"row\") %>% # add a total row\n adorn_percentages(denominator = \"col\") %>% # convert to proportions\n adorn_pct_formatting() %>% # convert to percents\n adorn_ns(position = \"front\") %>% # display as: \"count (percent)\"\n adorn_title( # adjust titles\n row_name = \"Age Category\",\n col_name = \"Gender\")\n\n Gender \n Age Category f m NA_\n 0-4 640 (22.8%) 416 (14.8%) 39 (14.0%)\n 5-9 641 (22.8%) 412 (14.7%) 42 (15.1%)\n 10-14 518 (18.5%) 383 (13.7%) 40 (14.4%)\n 15-19 359 (12.8%) 364 (13.0%) 20 (7.2%)\n 20-29 468 (16.7%) 575 (20.5%) 30 (10.8%)\n 30-49 179 (6.4%) 557 (19.9%) 18 (6.5%)\n 50-69 2 (0.1%) 91 (3.2%) 2 (0.7%)\n 70+ 0 (0.0%) 5 (0.2%) 1 (0.4%)\n <NA> 0 (0.0%) 0 (0.0%) 86 (30.9%)\n Total 2,807 (100.0%) 2,803 (100.0%) 278 (100.0%)\n\n\n\n\nPrinting the tabyl\nBy default, the tabyl will print raw to your R console.\nAlternatively, you can pass the tabyl to flextable or similar package to print as a “pretty” image in the RStudio Viewer, which could be exported as .png, .jpeg, .html, etc. This is discussed in the page Tables for presentation. Note that if printing in this manner and using adorn_titles(), you must specify placement = \"combined\".\n\nlinelist %>%\n tabyl(age_cat, gender) %>% \n adorn_totals(where = \"col\") %>% \n adorn_percentages(denominator = \"col\") %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\") %>% \n adorn_title(\n row_name = \"Age Category\",\n col_name = \"Gender\",\n placement = \"combined\") %>% # this is necessary to print as image\n flextable::flextable() %>% # convert to pretty image\n flextable::autofit() # format to one line per row \n\nAge Category/GenderfmNA_Total0-4640 (22.8%)416 (14.8%)39 (14.0%)1,095 (18.6%)5-9641 (22.8%)412 (14.7%)42 (15.1%)1,095 (18.6%)10-14518 (18.5%)383 (13.7%)40 (14.4%)941 (16.0%)15-19359 (12.8%)364 (13.0%)20 (7.2%)743 (12.6%)20-29468 (16.7%)575 (20.5%)30 (10.8%)1,073 (18.2%)30-49179 (6.4%)557 (19.9%)18 (6.5%)754 (12.8%)50-692 (0.1%)91 (3.2%)2 (0.7%)95 (1.6%)70+0 (0.0%)5 (0.2%)1 (0.4%)6 (0.1%)0 (0.0%)0 (0.0%)86 (30.9%)86 (1.5%)\n\n\n\n\nUse on other tables\nYou can use janitor’s adorn_*() functions on other tables, such as those created by summarise() and count() from dplyr, or table() from base R. Simply pipe the table to the desired janitor function. For example:\n\nlinelist %>% \n count(hospital) %>% # dplyr function\n adorn_totals() # janitor function\n\n hospital n\n Central Hospital 454\n Military Hospital 896\n Missing 1469\n Other 885\n Port Hospital 1762\n St. Mark's Maternity Hospital (SMMH) 422\n Total 5888\n\n\n\n\nSaving the tabyl\nIf you convert the table to a “pretty” image with a package like flextable, you can save it with functions from that package - like save_as_html(), save_as_word(), save_as_ppt(), and save_as_image() from flextable (as discussed more extensively in the Tables for presentation page). Below, the table is saved as a Word document, in which it can be further hand-edited.\n\nlinelist %>%\n tabyl(age_cat, gender) %>% \n adorn_totals(where = \"col\") %>% \n adorn_percentages(denominator = \"col\") %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\") %>% \n adorn_title(\n row_name = \"Age Category\",\n col_name = \"Gender\",\n placement = \"combined\") %>% \n flextable::flextable() %>% # convert to image\n flextable::autofit() %>% # ensure only one line per row\n flextable::save_as_docx(path = \"tabyl.docx\") # save as Word document to filepath\n\n\n\n\n\n\n\n\n\n\n\n\nStatistics\nYou can apply statistical tests on tabyls, like chisq.test() or fisher.test() from the stats package, as shown below. Note missing values are not allowed so they are excluded from the tabyl with show_na = FALSE.\n\nage_by_outcome <- linelist %>% \n tabyl(age_cat, outcome, show_na = FALSE) \n\nchisq.test(age_by_outcome)\n\n\n Pearson's Chi-squared test\n\ndata: age_by_outcome\nX-squared = 6.4931, df = 7, p-value = 0.4835\n\n\nSee the page on Simple statistical tests for more code and tips about statistics.\n\n\nOther tips\n\nInclude the argument na.rm = TRUE to exclude missing values from any of the above calculations.\n\nIf applying any adorn_*() helper functions to tables not created by tabyl(), you can specify particular column(s) to apply them to like adorn_percentage(,,,c(cases,deaths)) (specify them to the 4th unnamed argument). The syntax is not simple. Consider using summarise() instead.\n\nYou can read more detail in the janitor page and this tabyl vignette.", + "text": "17.3 janitor package\nThe janitor packages offers the tabyl() function to produce tabulations and cross-tabulations, which can be “adorned” or modified with helper functions to display percents, proportions, counts, etc.\nBelow, we pipe the linelist data frame to janitor functions and print the result. If desired, you can also save the resulting tables with the assignment operator <-.\n\nSimple tabyl\nThe default use of tabyl() on a specific column produces the unique values, counts, and column-wise “percents” (actually proportions). The proportions may have many digits. You can adjust the number of decimals with adorn_rounding() as described below.\n\nlinelist %>% tabyl(age_cat)\n\n age_cat n percent valid_percent\n 0-4 1095 0.185971467 0.188728025\n 5-9 1095 0.185971467 0.188728025\n 10-14 941 0.159816576 0.162185453\n 15-19 743 0.126188859 0.128059290\n 20-29 1073 0.182235054 0.184936229\n 30-49 754 0.128057065 0.129955188\n 50-69 95 0.016134511 0.016373664\n 70+ 6 0.001019022 0.001034126\n <NA> 86 0.014605978 NA\n\n\nAs you can see above, if there are missing values they display in a row labeled <NA>. You can suppress them with show_na = FALSE. If there are no missing values, this row will not appear. If there are missing values, all proportions are given as both raw (denominator inclusive of NA counts) and “valid” (denominator excludes NA counts).\nIf the column is class Factor and only certain levels are present in your data, all levels will still appear in the table. You can suppress this feature by specifying show_missing_levels = FALSE. Read more on the Factors page.\n\n\nCross-tabulation\nCross-tabulation counts are achieved by adding one or more additional columns within tabyl(). Note that now only counts are returned - proportions and percents can be added with additional steps shown below.\n\nlinelist %>% tabyl(age_cat, gender)\n\n age_cat f m NA_\n 0-4 640 416 39\n 5-9 641 412 42\n 10-14 518 383 40\n 15-19 359 364 20\n 20-29 468 575 30\n 30-49 179 557 18\n 50-69 2 91 2\n 70+ 0 5 1\n <NA> 0 0 86\n\n\n\n\n“Adorning” the tabyl\nUse janitor’s “adorn” functions to add totals or convert to proportions, percents, or otherwise adjust the display. Often, you will pipe the tabyl through several of these functions.\n\n\n\n\n\n\n\nFunction\nOutcome\n\n\n\n\nadorn_totals()\nAdds totals (where = “row”, “col”, or “both”). Set name = for “Total”.\n\n\nadorn_percentages()\nConvert counts to proportions, with denominator = “row”, “col”, or “all”.\n\n\nadorn_pct_formatting()\nConverts proportions to percents. Specify digits =. Remove the “%” symbol with affix_sign = FALSE.\n\n\nadorn_rounding()\nTo round proportions to digits = places. To round percents use adorn_pct_formatting() with digits =.\n\n\nadorn_ns()\nAdd counts to a table of proportions or percents. Indicate position = “rear” to show counts in parentheses, or “front” to put the percents in parentheses.\n\n\nadorn_title()\nAdd string via arguments row_name = and/or col_name =\n\n\n\nBe conscious of the order you apply the above functions. Below are some examples.\nA simple one-way table with percents instead of the default proportions.\n\nlinelist %>% # case linelist\n tabyl(age_cat) %>% # tabulate counts and proportions by age category\n adorn_pct_formatting() # convert proportions to percents\n\n age_cat n percent valid_percent\n 0-4 1095 18.6% 18.9%\n 5-9 1095 18.6% 18.9%\n 10-14 941 16.0% 16.2%\n 15-19 743 12.6% 12.8%\n 20-29 1073 18.2% 18.5%\n 30-49 754 12.8% 13.0%\n 50-69 95 1.6% 1.6%\n 70+ 6 0.1% 0.1%\n <NA> 86 1.5% -\n\n\nA cross-tabulation with a total row and row percents.\n\nlinelist %>% \n tabyl(age_cat, gender) %>% # counts by age and gender\n adorn_totals(where = \"row\") %>% # add total row\n adorn_percentages(denominator = \"row\") %>% # convert counts to proportions\n adorn_pct_formatting(digits = 1) # convert proportions to percents\n\n age_cat f m NA_\n 0-4 58.4% 38.0% 3.6%\n 5-9 58.5% 37.6% 3.8%\n 10-14 55.0% 40.7% 4.3%\n 15-19 48.3% 49.0% 2.7%\n 20-29 43.6% 53.6% 2.8%\n 30-49 23.7% 73.9% 2.4%\n 50-69 2.1% 95.8% 2.1%\n 70+ 0.0% 83.3% 16.7%\n <NA> 0.0% 0.0% 100.0%\n Total 47.7% 47.6% 4.7%\n\n\nA cross-tabulation adjusted so that both counts and percents are displayed.\n\nlinelist %>% # case linelist\n tabyl(age_cat, gender) %>% # cross-tabulate counts\n adorn_totals(where = \"row\") %>% # add a total row\n adorn_percentages(denominator = \"col\") %>% # convert to proportions\n adorn_pct_formatting() %>% # convert to percents\n adorn_ns(position = \"front\") %>% # display as: \"count (percent)\"\n adorn_title( # adjust titles\n row_name = \"Age Category\",\n col_name = \"Gender\")\n\n Gender \n Age Category f m NA_\n 0-4 640 (22.8%) 416 (14.8%) 39 (14.0%)\n 5-9 641 (22.8%) 412 (14.7%) 42 (15.1%)\n 10-14 518 (18.5%) 383 (13.7%) 40 (14.4%)\n 15-19 359 (12.8%) 364 (13.0%) 20 (7.2%)\n 20-29 468 (16.7%) 575 (20.5%) 30 (10.8%)\n 30-49 179 (6.4%) 557 (19.9%) 18 (6.5%)\n 50-69 2 (0.1%) 91 (3.2%) 2 (0.7%)\n 70+ 0 (0.0%) 5 (0.2%) 1 (0.4%)\n <NA> 0 (0.0%) 0 (0.0%) 86 (30.9%)\n Total 2,807 (100.0%) 2,803 (100.0%) 278 (100.0%)\n\n\n\n\nPrinting the tabyl\nBy default, the tabyl will print raw to your R console.\nAlternatively, you can pass the tabyl to flextable or similar package to print as a “pretty” image in the RStudio Viewer, which could be exported as .png, .jpeg, .html, etc. This is discussed in the page Tables for presentation. Note that if printing in this manner and using adorn_titles(), you must specify placement = \"combined\".\n\nlinelist %>%\n tabyl(age_cat, gender) %>% \n adorn_totals(where = \"col\") %>% \n adorn_percentages(denominator = \"col\") %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\") %>% \n adorn_title(\n row_name = \"Age Category\",\n col_name = \"Gender\",\n placement = \"combined\") %>% # this is necessary to print as image\n flextable::flextable() %>% # convert to pretty image\n flextable::autofit() # format to one line per row \n\nAge Category/GenderfmNA_Total0-4640 (22.8%)416 (14.8%)39 (14.0%)1,095 (18.6%)5-9641 (22.8%)412 (14.7%)42 (15.1%)1,095 (18.6%)10-14518 (18.5%)383 (13.7%)40 (14.4%)941 (16.0%)15-19359 (12.8%)364 (13.0%)20 (7.2%)743 (12.6%)20-29468 (16.7%)575 (20.5%)30 (10.8%)1,073 (18.2%)30-49179 (6.4%)557 (19.9%)18 (6.5%)754 (12.8%)50-692 (0.1%)91 (3.2%)2 (0.7%)95 (1.6%)70+0 (0.0%)5 (0.2%)1 (0.4%)6 (0.1%)0 (0.0%)0 (0.0%)86 (30.9%)86 (1.5%)\n\n\n\n\nUse on other tables\nYou can use janitor’s adorn_*() functions on other tables, such as those created by summarise() and count() from dplyr, or table() from base R. Simply pipe the table to the desired janitor function. For example:\n\nlinelist %>% \n count(hospital) %>% # dplyr function\n adorn_totals() # janitor function\n\n hospital n\n Central Hospital 454\n Military Hospital 896\n Missing 1469\n Other 885\n Port Hospital 1762\n St. Mark's Maternity Hospital (SMMH) 422\n Total 5888\n\n\n\n\nSaving the tabyl\nIf you convert the table to a “pretty” image with a package like flextable, you can save it with functions from that package - like save_as_html(), save_as_word(), save_as_ppt(), and save_as_image() from flextable (as discussed more extensively in the Tables for presentation page). Below, the table is saved as a Word document, in which it can be further hand-edited.\n\nlinelist %>%\n tabyl(age_cat, gender) %>% \n adorn_totals(where = \"col\") %>% \n adorn_percentages(denominator = \"col\") %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\") %>% \n adorn_title(\n row_name = \"Age Category\",\n col_name = \"Gender\",\n placement = \"combined\") %>% \n flextable::flextable() %>% # convert to image\n flextable::autofit() %>% # ensure only one line per row\n flextable::save_as_docx(path = \"tabyl.docx\") # save as Word document to filepath\n\n\n\n\n\n\n\n\n\n\n\n\nStatistics\nYou can apply statistical tests on tabyls, like chisq.test() or fisher.test() from the stats package, as shown below. Note missing values are not allowed so they are excluded from the tabyl with show_na = FALSE.\n\nage_by_outcome <- linelist %>% \n tabyl(age_cat, outcome, show_na = FALSE) \n\nchisq.test(age_by_outcome)\n\n\n Pearson's Chi-squared test\n\ndata: age_by_outcome\nX-squared = 6.4931, df = 7, p-value = 0.4835\n\n\nSee the page on Simple statistical tests for more code and tips about statistics.\n\n\nOther tips\n\nInclude the argument na.rm = TRUE to exclude missing values from any of the above calculations.\n\nIf applying any adorn_*() helper functions to tables not created by tabyl(), you can specify particular column(s) to apply them to like. adorn_percentage(,,,c(cases,deaths)) (specify them to the 4th unnamed argument). The syntax is not simple. Consider using summarise() instead.\n\nYou can read more detail in the janitor page and this tabyl vignette.", "crumbs": [ "Analysis", "17  Descriptive tables" @@ -1561,7 +1561,7 @@ "href": "new_pages/tables_descriptive.html#dplyr-package", "title": "17  Descriptive tables", "section": "17.4 dplyr package", - "text": "17.4 dplyr package\ndplyr is part of the tidyverse packages and is an very common data management tool. Creating tables with dplyr functions summarise() and count() is a useful approach to calculating summary statistics, summarize by group, or pass tables to ggplot().\nsummarise() creates a new, summary data frame. If the data are ungrouped, it will return a one-row dataframe with the specified summary statistics of the entire data frame. If the data are grouped, the new data frame will have one row per group (see Grouping data page).\nWithin the summarise() parentheses, you provide the names of each new summary column followed by an equals sign and a statistical function to apply.\nTIP: The summarise function works with both UK and US spelling (summarise() and summarize()).\n\nGet counts\nThe most simple function to apply within summarise() is n(). Leave the parentheses empty to count the number of rows.\n\nlinelist %>% # begin with linelist\n summarise(n_rows = n()) # return new summary dataframe with column n_rows\n\n n_rows\n1 5888\n\n\nThis gets more interesting if we have grouped the data beforehand.\n\nlinelist %>% \n group_by(age_cat) %>% # group data by unique values in column age_cat\n summarise(n_rows = n()) # return number of rows *per group*\n\n# A tibble: 9 × 2\n age_cat n_rows\n <fct> <int>\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 <NA> 86\n\n\nThe above command can be shortened by using the count() function instead. count() does the following:\n\nGroups the data by the columns provided to it\n\nSummarises them with n() (creating column n)\n\nUn-groups the data\n\n\nlinelist %>% \n count(age_cat)\n\n age_cat n\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 <NA> 86\n\n\nYou can change the name of the counts column from the default n to something else by specifying it to name =.\nTabulating counts of two or more grouping columns are still returned in “long” format, with the counts in the n column. See the page on Pivoting data to learn about “long” and “wide” data formats.\n\nlinelist %>% \n count(age_cat, outcome)\n\n age_cat outcome n\n1 0-4 Death 471\n2 0-4 Recover 364\n3 0-4 <NA> 260\n4 5-9 Death 476\n5 5-9 Recover 391\n6 5-9 <NA> 228\n7 10-14 Death 438\n8 10-14 Recover 303\n9 10-14 <NA> 200\n10 15-19 Death 323\n11 15-19 Recover 251\n12 15-19 <NA> 169\n13 20-29 Death 477\n14 20-29 Recover 367\n15 20-29 <NA> 229\n16 30-49 Death 329\n17 30-49 Recover 238\n18 30-49 <NA> 187\n19 50-69 Death 33\n20 50-69 Recover 38\n21 50-69 <NA> 24\n22 70+ Death 3\n23 70+ Recover 3\n24 <NA> Death 32\n25 <NA> Recover 28\n26 <NA> <NA> 26\n\n\n\n\nShow all levels\nIf you are tabling a column of class factor you can ensure that all levels are shown (not just the levels with values in the data) by adding .drop = FALSE into the summarise() or count() command.\nThis technique is useful to standardise your tables/plots. For example if you are creating figures for multiple sub-groups, or repeatedly creating the figure for routine reports. In each of these circumstances, the presence of values in the data may fluctuate, but you can define levels that remain constant.\nSee the page on Factors for more information.\n\n\nProportions\nProportions can be added by piping the table to mutate() to create a new column. Define the new column as the counts column (n by default) divided by the sum() of the counts column (this will return a proportion).\nNote that in this case, sum() in the mutate() command will return the sum of the whole column n for use as the proportion denominator. As explained in the Grouping data page, if sum() is used in grouped data (e.g. if the mutate() immediately followed a group_by() command), it will return sums by group. As stated just above, count() finishes its actions by ungrouping. Thus, in this scenario we get full column proportions.\nTo easily display percents, you can wrap the proportion in the function percent() from the package scales (note this convert to class character).\n\nage_summary <- linelist %>% \n count(age_cat) %>% # group and count by gender (produces \"n\" column)\n mutate( # create percent of column - note the denominator\n percent = scales::percent(n / sum(n))) \n\n# print\nage_summary\n\n age_cat n percent\n1 0-4 1095 18.60%\n2 5-9 1095 18.60%\n3 10-14 941 15.98%\n4 15-19 743 12.62%\n5 20-29 1073 18.22%\n6 30-49 754 12.81%\n7 50-69 95 1.61%\n8 70+ 6 0.10%\n9 <NA> 86 1.46%\n\n\nBelow is a method to calculate proportions within groups. It relies on different levels of data grouping being selectively applied and removed. First, the data are grouped on outcome via group_by(). Then, count() is applied. This function further groups the data by age_cat and returns counts for each outcome-age-cat combination. Importantly - as it finishes its process, count() also ungroups the age_cat grouping, so the only remaining data grouping is the original grouping by outcome. Thus, the final step of calculating proportions (denominator sum(n)) is still grouped by outcome.\n\nage_by_outcome <- linelist %>% # begin with linelist\n group_by(outcome) %>% # group by outcome \n count(age_cat) %>% # group and count by age_cat, and then remove age_cat grouping\n mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group\n\n\n\n\n\n\n\n\n\nPlotting\nTo display a “long” table output like the above with ggplot() is relatively straight-forward. The data are naturally in “long” format, which is naturally accepted by ggplot(). See further examples in the pages ggplot basics and ggplot tips.\n\nlinelist %>% # begin with linelist\n count(age_cat, outcome) %>% # group and tabulate counts by two columns\n ggplot()+ # pass new data frame to ggplot\n geom_col( # create bar plot\n mapping = aes( \n x = outcome, # map outcome to x-axis\n fill = age_cat, # map age_cat to the fill\n y = n)) # map the counts column `n` to the height\n\n\n\n\n\n\n\n\n\n\nSummary statistics\nOne major advantage of dplyr and summarise() is the ability to return more advanced statistical summaries like median(), mean(), max(), min(), sd() (standard deviation), and percentiles. You can also use sum() to return the number of rows that meet certain logical criteria. As above, these outputs can be produced for the whole data frame set, or by group.\nThe syntax is the same - within the summarise() parentheses you provide the names of each new summary column followed by an equals sign and a statistical function to apply. Within the statistical function, give the column(s) to be operated on and any relevant arguments (e.g. na.rm = TRUE for most mathematical functions).\nYou can also use sum() to return the number of rows that meet a logical criteria. The expression within is counted if it evaluates to TRUE. For example:\n\nsum(age_years < 18, na.rm=T)\n\nsum(gender == \"male\", na.rm=T)\n\nsum(response %in% c(\"Likely\", \"Very Likely\"))\n\nBelow, linelist data are summarised to describe the days delay from symptom onset to hospital admission (column days_onset_hosp), by hospital.\n\nsummary_table <- linelist %>% # begin with linelist, save out as new object\n group_by(hospital) %>% # group all calculations by hospital\n summarise( # only the below summary columns will be returned\n cases = n(), # number of rows per group\n delay_max = max(days_onset_hosp, na.rm = T), # max delay\n delay_mean = round(mean(days_onset_hosp, na.rm=T), digits = 1), # mean delay, rounded\n delay_sd = round(sd(days_onset_hosp, na.rm = T), digits = 1), # standard deviation of delays, rounded\n delay_3 = sum(days_onset_hosp >= 3, na.rm = T), # number of rows with delay of 3 or more days\n pct_delay_3 = scales::percent(delay_3 / cases) # convert previously-defined delay column to percent \n )\n\nsummary_table # print\n\n# A tibble: 6 × 7\n hospital cases delay_max delay_mean delay_sd delay_3 pct_delay_3\n <chr> <int> <dbl> <dbl> <dbl> <int> <chr> \n1 Central Hospital 454 12 1.9 1.9 108 24% \n2 Military Hospital 896 15 2.1 2.4 253 28% \n3 Missing 1469 22 2.1 2.3 399 27% \n4 Other 885 18 2 2.2 234 26% \n5 Port Hospital 1762 16 2.1 2.2 470 27% \n6 St. Mark's Maternity … 422 18 2.1 2.3 116 27% \n\n\nSome tips:\n\nUse sum() with a logic statement to “count” rows that meet certain criteria (==)\n\nNote the use of na.rm = TRUE within mathematical functions like sum(), otherwise NA will be returned if there are any missing values\n\nUse the function percent() from the scales package to easily convert to percents\n\nSet accuracy = to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively\n\n\nUse round() from base R to specify decimals\n\nTo calculate these statistics on the entire dataset, use summarise() without group_by()\n\nYou may create columns for the purposes of later calculations (e.g. denominators) that you eventually drop from your data frame with select().\n\n\n\nConditional statistics\nYou may want to return conditional statistics - e.g. the maximum of rows that meet certain criteria. This can be done by subsetting the column with brackets [ ]. The example below returns the maximum temperature for patients classified having or not having fever. Be aware however - it may be more appropriate to add another column to the group_by() command and pivot_wider() (as demonstrated below).\n\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n max_temp_fvr = max(temp[fever == \"yes\"], na.rm = T),\n max_temp_no = max(temp[fever == \"no\"], na.rm = T)\n )\n\n# A tibble: 6 × 3\n hospital max_temp_fvr max_temp_no\n <chr> <dbl> <dbl>\n1 Central Hospital 40.4 38 \n2 Military Hospital 40.5 38 \n3 Missing 40.6 38 \n4 Other 40.8 37.9\n5 Port Hospital 40.6 38 \n6 St. Mark's Maternity Hospital (SMMH) 40.6 37.9\n\n\n\n\nGlueing together\nThe function str_glue() from stringr is useful to combine values from several columns into one new column. In this context this is typically used after the summarise() command.\nIn the Characters and strings page, various options for combining columns are discussed, including unite(), and paste0(). In this use case, we advocate for str_glue() because it is more flexible than unite() and has more simple syntax than paste0().\nBelow, the summary_table data frame (created above) is mutated such that columns delay_mean and delay_sd are combined, parentheses formating is added to the new column, and their respective old columns are removed.\nThen, to make the table more presentable, a total row is added with adorn_totals() from janitor (which ignores non-numeric columns). Lastly, we use select() from dplyr to both re-order and rename to nicer column names.\nNow you could pass to flextable and print the table to Word, .png, .jpeg, .html, Powerpoint, RMarkdown, etc.! (see the Tables for presentation page).\n\nsummary_table %>% \n mutate(delay = str_glue(\"{delay_mean} ({delay_sd})\")) %>% # combine and format other values\n select(-c(delay_mean, delay_sd)) %>% # remove two old columns \n adorn_totals(where = \"row\") %>% # add total row\n select( # order and rename cols\n \"Hospital Name\" = hospital,\n \"Cases\" = cases,\n \"Max delay\" = delay_max,\n \"Mean (sd)\" = delay,\n \"Delay 3+ days\" = delay_3,\n \"% delay 3+ days\" = pct_delay_3\n )\n\n Hospital Name Cases Max delay Mean (sd) Delay 3+ days\n Central Hospital 454 12 1.9 (1.9) 108\n Military Hospital 896 15 2.1 (2.4) 253\n Missing 1469 22 2.1 (2.3) 399\n Other 885 18 2 (2.2) 234\n Port Hospital 1762 16 2.1 (2.2) 470\n St. Mark's Maternity Hospital (SMMH) 422 18 2.1 (2.3) 116\n Total 5888 101 - 1580\n % delay 3+ days\n 24%\n 28%\n 27%\n 26%\n 27%\n 27%\n -\n\n\n\nPercentiles\nPercentiles and quantiles in dplyr deserve a special mention. To return quantiles, use quantile() with the defaults or specify the value(s) you would like with probs =.\n\n# get default percentile values of age (0%, 25%, 50%, 75%, 100%)\nlinelist %>% \n summarise(age_percentiles = quantile(age_years, na.rm = TRUE))\n\nWarning: Returning more (or less) than 1 row per `summarise()` group was deprecated in\ndplyr 1.1.0.\nℹ Please use `reframe()` instead.\nℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`\n always returns an ungrouped data frame and adjust accordingly.\n\n\n age_percentiles\n1 0\n2 6\n3 13\n4 23\n5 84\n\n# get manually-specified percentile values of age (5%, 50%, 75%, 98%)\nlinelist %>% \n summarise(\n age_percentiles = quantile(\n age_years,\n probs = c(.05, 0.5, 0.75, 0.98), \n na.rm=TRUE)\n )\n\nWarning: Returning more (or less) than 1 row per `summarise()` group was deprecated in\ndplyr 1.1.0.\nℹ Please use `reframe()` instead.\nℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`\n always returns an ungrouped data frame and adjust accordingly.\n\n\n age_percentiles\n1 1\n2 13\n3 23\n4 48\n\n\nIf you want to return quantiles by group, you may encounter long and less useful outputs if you simply add another column to group_by(). So, try this approach instead - create a column for each quantile level desired.\n\n# get manually-specified percentile values of age (5%, 50%, 75%, 98%)\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n p05 = quantile(age_years, probs = 0.05, na.rm=T),\n p50 = quantile(age_years, probs = 0.5, na.rm=T),\n p75 = quantile(age_years, probs = 0.75, na.rm=T),\n p98 = quantile(age_years, probs = 0.98, na.rm=T)\n )\n\n# A tibble: 6 × 5\n hospital p05 p50 p75 p98\n <chr> <dbl> <dbl> <dbl> <dbl>\n1 Central Hospital 1 12 21 48 \n2 Military Hospital 1 13 24 45 \n3 Missing 1 13 23 48.2\n4 Other 1 13 23 50 \n5 Port Hospital 1 14 24 49 \n6 St. Mark's Maternity Hospital (SMMH) 2 12 22 50.2\n\n\nWhile dplyr summarise() certainly offers more fine control, you may find that all the summary statistics you need can be produced with get_summary_stat() from the rstatix package. If operating on grouped data, if will return 0%, 25%, 50%, 75%, and 100%. If applied to ungrouped data, you can specify the percentiles with probs = c(.05, .5, .75, .98).\n\nlinelist %>% \n group_by(hospital) %>% \n rstatix::get_summary_stats(age, type = \"quantile\")\n\n# A tibble: 6 × 8\n hospital variable n `0%` `25%` `50%` `75%` `100%`\n <chr> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 Central Hospital age 445 0 6 12 21 58\n2 Military Hospital age 884 0 6 14 24 72\n3 Missing age 1441 0 6 13 23 76\n4 Other age 873 0 6 13 23 69\n5 Port Hospital age 1739 0 6 14 24 68\n6 St. Mark's Maternity Hospital (… age 420 0 7 12 22 84\n\n\n\nlinelist %>% \n rstatix::get_summary_stats(age, type = \"quantile\")\n\n# A tibble: 1 × 7\n variable n `0%` `25%` `50%` `75%` `100%`\n <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 age 5802 0 6 13 23 84\n\n\n\n\n\nSummarise aggregated data\nIf you begin with aggregated data, using n() return the number of rows, not the sum of the aggregated counts. To get sums, use sum() on the data’s counts column.\nFor example, let’s say you are beginning with the data frame of counts below, called linelist_agg - it shows in “long” format the case counts by outcome and gender.\nBelow we create this example data frame of linelist case counts by outcome and gender (missing values removed for clarity).\n\nlinelist_agg <- linelist %>% \n drop_na(gender, outcome) %>% \n count(outcome, gender)\n\nlinelist_agg\n\n outcome gender n\n1 Death f 1227\n2 Death m 1228\n3 Recover f 953\n4 Recover m 950\n\n\nTo sum the counts (in column n) by group you can use summarise() but set the new column equal to sum(n, na.rm=T). To add a conditional element to the sum operation, you can use the subset bracket [ ] syntax on the counts column.\n\nlinelist_agg %>% \n group_by(outcome) %>% \n summarise(\n total_cases = sum(n, na.rm=T),\n male_cases = sum(n[gender == \"m\"], na.rm=T),\n female_cases = sum(n[gender == \"f\"], na.rm=T))\n\n# A tibble: 2 × 4\n outcome total_cases male_cases female_cases\n <chr> <int> <int> <int>\n1 Death 2455 1228 1227\n2 Recover 1903 950 953\n\n\n\n\nacross() multiple columns\nYou can use summarise() across multiple columns using across(). This makes life easier when you want to calculate the same statistics for many columns. Place across() within summarise() and specify the following:\n\n.cols = as either a vector of column names c() or “tidyselect” helper functions (explained below)\n\n.fns = the function to perform (no parentheses) - you can provide multiple within a list()\n\nBelow, mean() is applied to several numeric columns. A vector of columns are named explicitly to .cols = and a single function mean is specified (no parentheses) to .fns =. Any additional arguments for the function (e.g. na.rm=TRUE) are provided after .fns =, separated by a comma.\nIt can be difficult to get the order of parentheses and commas correct when using across(). Remember that within across() you must include the columns, the functions, and any extra arguments needed for the functions.\n\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns\n .fns = mean, # function\n na.rm=T)) # extra arguments\n\nWarning: There was 1 warning in `summarise()`.\nℹ In argument: `across(...)`.\nℹ In group 1: `outcome = \"Death\"`.\nCaused by warning:\n! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.\nSupply arguments directly to `.fns` through an anonymous function instead.\n\n # Previously\n across(a:b, mean, na.rm = TRUE)\n\n # Now\n across(a:b, \\(x) mean(x, na.rm = TRUE))\n\n\n# A tibble: 3 × 5\n outcome age_years temp wt_kg ht_cm\n <chr> <dbl> <dbl> <dbl> <dbl>\n1 Death 15.9 38.6 52.6 125.\n2 Recover 16.1 38.6 52.5 125.\n3 <NA> 16.2 38.6 53.0 125.\n\n\nMultiple functions can be run at once. Below the functions mean and sd are provided to .fns = within a list(). You have the opportunity to provide character names (e.g. “mean” and “sd”) which are appended in the new column names.\n\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns\n .fns = list(\"mean\" = mean, \"sd\" = sd), # multiple functions \n na.rm=T)) # extra arguments\n\n# A tibble: 3 × 9\n outcome age_years_mean age_years_sd temp_mean temp_sd wt_kg_mean wt_kg_sd\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 Death 15.9 12.3 38.6 0.962 52.6 18.4\n2 Recover 16.1 13.0 38.6 0.997 52.5 18.6\n3 <NA> 16.2 12.8 38.6 0.976 53.0 18.9\n# ℹ 2 more variables: ht_cm_mean <dbl>, ht_cm_sd <dbl>\n\n\nHere are those “tidyselect” helper functions you can provide to .cols = to select columns:\n\neverything() - all other columns not mentioned\n\nlast_col() - the last column\n\nwhere() - applies a function to all columns and selects those which are TRUE\n\nstarts_with() - matches to a specified prefix. Example: starts_with(\"date\")\nends_with() - matches to a specified suffix. Example: ends_with(\"_end\")\n\ncontains() - columns containing a character string. Example: contains(\"time\")\nmatches() - to apply a regular expression (regex). Example: contains(\"[pt]al\")\n\nnum_range() -\nany_of() - matches if column is named. Useful if the name might not exist. Example: any_of(date_onset, date_death, cardiac_arrest)\n\nFor example, to return the mean of every numeric column use where() and provide the function as.numeric() (without parentheses). All this remains within the across() command.\n\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(\n .cols = where(is.numeric), # all numeric columns in the data frame\n .fns = mean,\n na.rm=T))\n\n# A tibble: 3 × 12\n outcome generation age age_years lon lat wt_kg ht_cm ct_blood temp\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 Death 16.7 15.9 15.9 -13.2 8.47 52.6 125. 21.3 38.6\n2 Recover 16.4 16.2 16.1 -13.2 8.47 52.5 125. 21.1 38.6\n3 <NA> 16.5 16.3 16.2 -13.2 8.47 53.0 125. 21.2 38.6\n# ℹ 2 more variables: bmi <dbl>, days_onset_hosp <dbl>\n\n\n\n\nPivot wider\nIf you prefer your table in “wide” format you can transform it using the tidyr pivot_wider() function. You will likely need to re-name the columns with rename(). For more information see the page on Pivoting data.\nThe example below begins with the “long” table age_by_outcome from the proportions section. We create it again and print, for clarity:\n\nage_by_outcome <- linelist %>% # begin with linelist\n group_by(outcome) %>% # group by outcome \n count(age_cat) %>% # group and count by age_cat, and then remove age_cat grouping\n mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group\n\n\n\n\n\n\n\nTo pivot wider, we create the new columns from the values in the existing column age_cat (by setting names_from = age_cat). We also specify that the new table values will come from the existing column n, with values_from = n. The columns not mentioned in our pivoting command (outcome) will remain unchanged on the far left side.\n\nage_by_outcome %>% \n select(-percent) %>% # keep only counts for simplicity\n pivot_wider(names_from = age_cat, values_from = n) \n\n# A tibble: 3 × 10\n# Groups: outcome [3]\n outcome `0-4` `5-9` `10-14` `15-19` `20-29` `30-49` `50-69` `70+` `NA`\n <chr> <int> <int> <int> <int> <int> <int> <int> <int> <int>\n1 Death 471 476 438 323 477 329 33 3 32\n2 Recover 364 391 303 251 367 238 38 3 28\n3 <NA> 260 228 200 169 229 187 24 NA 26\n\n\n\n\nTotal rows\nWhen summarise() operates on grouped data it does not automatically produce “total” statistics. Below, two approaches to adding a total row are presented:\n\njanitor’s adorn_totals()\nIf your table consists only of counts or proportions/percents that can be summed into a total, then you can add sum totals using janitor’s adorn_totals() as described in the section above. Note that this function can only sum the numeric columns - if you want to calculate other total summary statistics see the next approach with dplyr.\nBelow, linelist is grouped by gender and summarised into a table that described the number of cases with known outcome, deaths, and recovered. Piping the table to adorn_totals() adds a total row at the bottom reflecting the sum of each column. The further adorn_*() functions adjust the display as noted in the code.\n\nlinelist %>% \n group_by(gender) %>%\n summarise(\n known_outcome = sum(!is.na(outcome)), # Number of rows in group where outcome is not missing\n n_death = sum(outcome == \"Death\", na.rm=T), # Number of rows in group where outcome is Death\n n_recover = sum(outcome == \"Recover\", na.rm=T), # Number of rows in group where outcome is Recovered\n ) %>% \n adorn_totals() %>% # Adorn total row (sums of each numeric column)\n adorn_percentages(\"col\") %>% # Get column proportions\n adorn_pct_formatting() %>% # Convert proportions to percents\n adorn_ns(position = \"front\") # display % and counts (with counts in front)\n\n gender known_outcome n_death n_recover\n f 2,180 (47.8%) 1,227 (47.5%) 953 (48.1%)\n m 2,178 (47.7%) 1,228 (47.6%) 950 (47.9%)\n <NA> 207 (4.5%) 127 (4.9%) 80 (4.0%)\n Total 4,565 (100.0%) 2,582 (100.0%) 1,983 (100.0%)\n\n\n\n\nsummarise() on “total” data and then bind_rows()\nIf your table consists of summary statistics such as median(), mean(), etc, the adorn_totals() approach shown above will not be sufficient. Instead, to get summary statistics for the entire dataset you must calculate them with a separate summarise() command and then bind the results to the original grouped summary table. To do the binding you can use bind_rows() from dplyr s described in the Joining data page. Below is an example:\nYou can make a summary table of outcome by hospital with group_by() and summarise() like this:\n\nby_hospital <- linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>% # Remove cases with missing outcome or hospital\n group_by(hospital, outcome) %>% # Group data\n summarise( # Create new summary columns of indicators of interest\n N = n(), # Number of rows per hospital-outcome group \n ct_value = median(ct_blood, na.rm=T)) # median CT value per group\n \nby_hospital # print table\n\n# A tibble: 10 × 4\n# Groups: hospital [5]\n hospital outcome N ct_value\n <chr> <chr> <int> <dbl>\n 1 Central Hospital Death 193 22\n 2 Central Hospital Recover 165 22\n 3 Military Hospital Death 399 21\n 4 Military Hospital Recover 309 22\n 5 Other Death 395 22\n 6 Other Recover 290 21\n 7 Port Hospital Death 785 22\n 8 Port Hospital Recover 579 21\n 9 St. Mark's Maternity Hospital (SMMH) Death 199 22\n10 St. Mark's Maternity Hospital (SMMH) Recover 126 22\n\n\nTo get the totals, run the same summarise() command but only group the data by outcome (not by hospital), like this:\n\ntotals <- linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>%\n group_by(outcome) %>% # Grouped only by outcome, not by hospital \n summarise(\n N = n(), # These statistics are now by outcome only \n ct_value = median(ct_blood, na.rm=T))\n\ntotals # print table\n\n# A tibble: 2 × 3\n outcome N ct_value\n <chr> <int> <dbl>\n1 Death 1971 22\n2 Recover 1469 22\n\n\nWe can bind these two data frames together. Note that by_hospital has 4 columns whereas totals has 3 columns. By using bind_rows(), the columns are combined by name, and any extra space is filled in with NA (e.g the column hospital values for the two new totals rows). After binding the rows, we convert these empty spaces to “Total” using replace_na() (see Cleaning data and core functions page).\n\ntable_long <- bind_rows(by_hospital, totals) %>% \n mutate(hospital = replace_na(hospital, \"Total\"))\n\nHere is the new table with “Total” rows at the bottom.\n\n\n\n\n\n\nThis table is in a “long” format, which may be what you want. Optionally, you can pivot this table wider to make it more readable. See the section on pivoting wider above, and the Pivoting data page. You can also add more columns, and arrange it nicely. This code is below.\n\ntable_long %>% \n \n # Pivot wider and format\n ########################\n mutate(hospital = replace_na(hospital, \"Total\")) %>% \n pivot_wider( # Pivot from long to wide\n values_from = c(ct_value, N), # new values are from ct and count columns\n names_from = outcome) %>% # new column names are from outcomes\n mutate( # Add new columns\n N_Known = N_Death + N_Recover, # number with known outcome\n Pct_Death = scales::percent(N_Death / N_Known, 0.1), # percent cases who died (to 1 decimal)\n Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)\n select( # Re-order columns\n hospital, N_Known, # Intro columns\n N_Recover, Pct_Recover, ct_value_Recover, # Recovered columns\n N_Death, Pct_Death, ct_value_Death) %>% # Death columns\n arrange(N_Known) # Arrange rows from lowest to highest (Total row at bottom)\n\n# A tibble: 6 × 8\n# Groups: hospital [6]\n hospital N_Known N_Recover Pct_Recover ct_value_Recover N_Death Pct_Death\n <chr> <int> <int> <chr> <dbl> <int> <chr> \n1 St. Mark's M… 325 126 38.8% 22 199 61.2% \n2 Central Hosp… 358 165 46.1% 22 193 53.9% \n3 Other 685 290 42.3% 21 395 57.7% \n4 Military Hos… 708 309 43.6% 22 399 56.4% \n5 Port Hospital 1364 579 42.4% 21 785 57.6% \n6 Total 3440 1469 42.7% 22 1971 57.3% \n# ℹ 1 more variable: ct_value_Death <dbl>\n\n\nAnd then you can print this nicely as an image - below is the output printed with flextable. You can read more in depth about this example and how to achieve this “pretty” table in the Tables for presentation page.\n\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22", + "text": "17.4 dplyr package\ndplyr is part of the tidyverse packages and is an very common data management tool. Creating tables with dplyr functions summarise() and count() is a useful approach to calculating summary statistics, summarize by group, or pass tables to ggplot().\nsummarise() creates a new, summary data frame. If the data are ungrouped, it will return a one-row dataframe with the specified summary statistics of the entire data frame. If the data are grouped, the new data frame will have one row per group (see Grouping data page).\nWithin the summarise() parentheses, you provide the names of each new summary column followed by an equals sign and a statistical function to apply.\nTIP: The summarise function works with both UK and US spelling (summarise() and summarize()).\n\nGet counts\nThe most simple function to apply within summarise() is n(). Leave the parentheses empty to count the number of rows.\n\nlinelist %>% # begin with linelist\n summarise(n_rows = n()) # return new summary dataframe with column n_rows\n\n n_rows\n1 5888\n\n\nThis gets more interesting if we have grouped the data beforehand.\n\nlinelist %>% \n group_by(age_cat) %>% # group data by unique values in column age_cat\n summarise(n_rows = n()) # return number of rows *per group*\n\n# A tibble: 9 × 2\n age_cat n_rows\n <fct> <int>\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 <NA> 86\n\n\nThe above command can be shortened by using the count() function instead. count() does the following:\n\nGroups the data by the columns provided to it.\n\nSummarises them with n() (creating column n).\n\nUn-groups the data.\n\n\nlinelist %>% \n count(age_cat)\n\n age_cat n\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 <NA> 86\n\n\nYou can change the name of the counts column from the default n to something else by specifying it to name =.\nTabulating counts of two or more grouping columns are still returned in “long” format, with the counts in the n column. See the page on Pivoting data to learn about “long” and “wide” data formats.\n\nlinelist %>% \n count(age_cat, outcome)\n\n age_cat outcome n\n1 0-4 Death 471\n2 0-4 Recover 364\n3 0-4 <NA> 260\n4 5-9 Death 476\n5 5-9 Recover 391\n6 5-9 <NA> 228\n7 10-14 Death 438\n8 10-14 Recover 303\n9 10-14 <NA> 200\n10 15-19 Death 323\n11 15-19 Recover 251\n12 15-19 <NA> 169\n13 20-29 Death 477\n14 20-29 Recover 367\n15 20-29 <NA> 229\n16 30-49 Death 329\n17 30-49 Recover 238\n18 30-49 <NA> 187\n19 50-69 Death 33\n20 50-69 Recover 38\n21 50-69 <NA> 24\n22 70+ Death 3\n23 70+ Recover 3\n24 <NA> Death 32\n25 <NA> Recover 28\n26 <NA> <NA> 26\n\n\n\n\nShow all levels\nIf you are tabling a column of class factor you can ensure that all levels are shown (not just the levels with values in the data) by adding .drop = FALSE into the summarise() or count() command.\nThis technique is useful to standardise your tables/plots. For example if you are creating figures for multiple sub-groups, or repeatedly creating the figure for routine reports. In each of these circumstances, the presence of values in the data may fluctuate, but you can define levels that remain constant.\nSee the page on Factors for more information.\n\n\nProportions\nProportions can be added by piping the table to mutate() to create a new column. Define the new column as the counts column (n by default) divided by the sum() of the counts column (this will return a proportion).\nNote that in this case, sum() in the mutate() command will return the sum of the whole column n for use as the proportion denominator. As explained in the Grouping data page, if sum() is used in grouped data (e.g. if the mutate() immediately followed a group_by() command), it will return sums by group. As stated just above, count() finishes its actions by ungrouping. Thus, in this scenario we get full column proportions.\nTo easily display percents, you can wrap the proportion in the function percent() from the package scales (note this convert to class character).\n\nage_summary <- linelist %>% \n count(age_cat) %>% # group and count by gender (produces \"n\" column)\n mutate( # create percent of column - note the denominator\n percent = scales::percent(n / sum(n))\n ) \n\n# print\nage_summary\n\n age_cat n percent\n1 0-4 1095 18.60%\n2 5-9 1095 18.60%\n3 10-14 941 15.98%\n4 15-19 743 12.62%\n5 20-29 1073 18.22%\n6 30-49 754 12.81%\n7 50-69 95 1.61%\n8 70+ 6 0.10%\n9 <NA> 86 1.46%\n\n\nBelow is a method to calculate proportions within groups. It relies on different levels of data grouping being selectively applied and removed. First, the data are grouped on outcome via group_by(). Then, count() is applied. This function further groups the data by age_cat and returns counts for each outcome-age-cat combination. Importantly - as it finishes its process, count() also ungroups the age_cat grouping, so the only remaining data grouping is the original grouping by outcome. Thus, the final step of calculating proportions (denominator sum(n)) is still grouped by outcome.\n\nage_by_outcome <- linelist %>% # begin with linelist\n group_by(outcome) %>% # group by outcome \n count(age_cat) %>% # group and count by age_cat, and then remove age_cat grouping\n mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group\n\n\n\n\n\n\n\n\n\nPlotting\nTo display a “long” table output like the above with ggplot() is relatively straight-forward. The data are naturally in “long” format, which is naturally accepted by ggplot(). See further examples in the pages ggplot basics and ggplot tips.\n\nlinelist %>% # begin with linelist\n count(age_cat, outcome) %>% # group and tabulate counts by two columns\n ggplot()+ # pass new data frame to ggplot\n geom_col( # create bar plot\n mapping = aes( \n x = outcome, # map outcome to x-axis\n fill = age_cat, # map age_cat to the fill\n y = n)) # map the counts column `n` to the height\n\n\n\n\n\n\n\n\n\n\nSummary statistics\nOne major advantage of dplyr and summarise() is the ability to return more advanced statistical summaries like median(), mean(), max(), min(), sd() (standard deviation), and percentiles. You can also use sum() to return the number of rows that meet certain logical criteria. As above, these outputs can be produced for the whole data frame set, or by group.\nThe syntax is the same - within the summarise() parentheses you provide the names of each new summary column followed by an equals sign and a statistical function to apply. Within the statistical function, give the column(s) to be operated on and any relevant arguments (e.g. na.rm = TRUE for most mathematical functions).\nYou can also use sum() to return the number of rows that meet a logical criteria. The expression within is counted if it evaluates to TRUE. For example:\n\nsum(age_years < 18, na.rm=T)\n\nsum(gender == \"male\", na.rm=T)\n\nsum(response %in% c(\"Likely\", \"Very Likely\"))\n\nBelow, linelist data are summarised to describe the days delay from symptom onset to hospital admission (column days_onset_hosp), by hospital.\n\nsummary_table <- linelist %>% # begin with linelist, save out as new object\n group_by(hospital) %>% # group all calculations by hospital\n summarise( # only the below summary columns will be returned\n cases = n(), # number of rows per group\n delay_max = max(days_onset_hosp, na.rm = T), # max delay\n delay_mean = round(mean(days_onset_hosp, na.rm=T), digits = 1), # mean delay, rounded\n delay_sd = round(sd(days_onset_hosp, na.rm = T), digits = 1), # standard deviation of delays, rounded\n delay_3 = sum(days_onset_hosp >= 3, na.rm = T), # number of rows with delay of 3 or more days\n pct_delay_3 = scales::percent(delay_3 / cases) # convert previously-defined delay column to percent \n )\n\nsummary_table # print\n\n# A tibble: 6 × 7\n hospital cases delay_max delay_mean delay_sd delay_3 pct_delay_3\n <chr> <int> <dbl> <dbl> <dbl> <int> <chr> \n1 Central Hospital 454 12 1.9 1.9 108 24% \n2 Military Hospital 896 15 2.1 2.4 253 28% \n3 Missing 1469 22 2.1 2.3 399 27% \n4 Other 885 18 2 2.2 234 26% \n5 Port Hospital 1762 16 2.1 2.2 470 27% \n6 St. Mark's Maternity … 422 18 2.1 2.3 116 27% \n\n\nSome tips:\n\nUse sum() with a logic statement to “count” rows that meet certain criteria (==).\n\nNote the use of na.rm = TRUE within mathematical functions like sum(), otherwise NA will be returned if there are any missing values.\n\nUse the function percent() from the scales package to easily convert to percents.\n\nSet accuracy = to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively.\n\n\nUse round() from base R to specify decimals.\n\nTo calculate these statistics on the entire dataset, use summarise() without group_by().\nYou may create columns for the purposes of later calculations (e.g. denominators) that you eventually drop from your data frame with select().\n\n\n\nConditional statistics\nYou may want to return conditional statistics - e.g. the maximum of rows that meet certain criteria. This can be done by subsetting the column with brackets [ ]. The example below returns the maximum temperature for patients classified having or not having fever. Be aware however - it may be more appropriate to add another column to the group_by() command and pivot_wider() (as demonstrated below).\n\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n max_temp_fvr = max(temp[fever == \"yes\"], na.rm = T),\n max_temp_no = max(temp[fever == \"no\"], na.rm = T)\n )\n\n# A tibble: 6 × 3\n hospital max_temp_fvr max_temp_no\n <chr> <dbl> <dbl>\n1 Central Hospital 40.4 38 \n2 Military Hospital 40.5 38 \n3 Missing 40.6 38 \n4 Other 40.8 37.9\n5 Port Hospital 40.6 38 \n6 St. Mark's Maternity Hospital (SMMH) 40.6 37.9\n\n\n\n\nGlueing together\nThe function str_glue() from stringr is useful to combine values from several columns into one new column. In this context this is typically used after the summarise() command.\nIn the Characters and strings page, various options for combining columns are discussed, including unite(), and paste0(). In this use case, we advocate for str_glue() because it is more flexible than unite() and has more simple syntax than paste0().\nBelow, the summary_table data frame (created above) is mutated such that columns delay_mean and delay_sd are combined, parentheses formating is added to the new column, and their respective old columns are removed.\nThen, to make the table more presentable, a total row is added with adorn_totals() from janitor (which ignores non-numeric columns). Lastly, we use select() from dplyr to both re-order and rename to nicer column names.\nNow you could pass to flextable and print the table to Word, .png, .jpeg, .html, Powerpoint, RMarkdown, etc.! (see the Tables for presentation page).\n\nsummary_table %>% \n mutate(delay = str_glue(\"{delay_mean} ({delay_sd})\")) %>% # combine and format other values\n select(-c(delay_mean, delay_sd)) %>% # remove two old columns \n adorn_totals(where = \"row\") %>% # add total row\n select( # order and rename cols\n \"Hospital Name\" = hospital,\n \"Cases\" = cases,\n \"Max delay\" = delay_max,\n \"Mean (sd)\" = delay,\n \"Delay 3+ days\" = delay_3,\n \"% delay 3+ days\" = pct_delay_3\n )\n\n Hospital Name Cases Max delay Mean (sd) Delay 3+ days\n Central Hospital 454 12 1.9 (1.9) 108\n Military Hospital 896 15 2.1 (2.4) 253\n Missing 1469 22 2.1 (2.3) 399\n Other 885 18 2 (2.2) 234\n Port Hospital 1762 16 2.1 (2.2) 470\n St. Mark's Maternity Hospital (SMMH) 422 18 2.1 (2.3) 116\n Total 5888 101 - 1580\n % delay 3+ days\n 24%\n 28%\n 27%\n 26%\n 27%\n 27%\n -\n\n\n\nPercentiles\nPercentiles and quantiles in dplyr deserve a special mention. To return quantiles, use quantile() with the defaults or specify the value(s) you would like with probs =.\n\n# get default percentile values of age (0%, 25%, 50%, 75%, 100%)\nlinelist %>% \n summarise(age_percentiles = quantile(age_years, na.rm = TRUE))\n\n age_percentiles\n1 0\n2 6\n3 13\n4 23\n5 84\n\n# get manually-specified percentile values of age (5%, 50%, 75%, 98%)\nlinelist %>% \n summarise(\n age_percentiles = quantile(\n age_years,\n probs = c(.05, 0.5, 0.75, 0.98), \n na.rm=TRUE)\n )\n\n age_percentiles\n1 1\n2 13\n3 23\n4 48\n\n\nIf you want to return quantiles by group, you may encounter long and less useful outputs if you simply add another column to group_by(). So, try this approach instead - create a column for each quantile level desired.\n\n# get manually-specified percentile values of age (5%, 50%, 75%, 98%)\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n p05 = quantile(age_years, probs = 0.05, na.rm=T),\n p50 = quantile(age_years, probs = 0.5, na.rm=T),\n p75 = quantile(age_years, probs = 0.75, na.rm=T),\n p98 = quantile(age_years, probs = 0.98, na.rm=T)\n )\n\n# A tibble: 6 × 5\n hospital p05 p50 p75 p98\n <chr> <dbl> <dbl> <dbl> <dbl>\n1 Central Hospital 1 12 21 48 \n2 Military Hospital 1 13 24 45 \n3 Missing 1 13 23 48.2\n4 Other 1 13 23 50 \n5 Port Hospital 1 14 24 49 \n6 St. Mark's Maternity Hospital (SMMH) 2 12 22 50.2\n\n\nWhile dplyr summarise() certainly offers more fine control, you may find that all the summary statistics you need can be produced with get_summary_stat() from the rstatix package. If operating on grouped data, if will return 0%, 25%, 50%, 75%, and 100%. If applied to ungrouped data, you can specify the percentiles with probs = c(.05, .5, .75, .98).\n\nlinelist %>% \n group_by(hospital) %>% \n rstatix::get_summary_stats(age, type = \"quantile\")\n\n# A tibble: 6 × 8\n hospital variable n `0%` `25%` `50%` `75%` `100%`\n <chr> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 Central Hospital age 445 0 6 12 21 58\n2 Military Hospital age 884 0 6 14 24 72\n3 Missing age 1441 0 6 13 23 76\n4 Other age 873 0 6 13 23 69\n5 Port Hospital age 1739 0 6 14 24 68\n6 St. Mark's Maternity Hospital (… age 420 0 7 12 22 84\n\n\n\nlinelist %>% \n rstatix::get_summary_stats(age, type = \"quantile\")\n\n# A tibble: 1 × 7\n variable n `0%` `25%` `50%` `75%` `100%`\n <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 age 5802 0 6 13 23 84\n\n\n\n\n\nSummarise aggregated data\nIf you begin with aggregated data, using n() return the number of rows, not the sum of the aggregated counts. To get sums, use sum() on the data’s counts column.\nFor example, let’s say you are beginning with the data frame of counts below, called linelist_agg - it shows in “long” format the case counts by outcome and gender.\nBelow we create this example data frame of linelist case counts by outcome and gender (missing values removed for clarity).\n\nlinelist_agg <- linelist %>% \n drop_na(gender, outcome) %>% \n count(outcome, gender)\n\nlinelist_agg\n\n outcome gender n\n1 Death f 1227\n2 Death m 1228\n3 Recover f 953\n4 Recover m 950\n\n\nTo sum the counts (in column n) by group you can use summarise() but set the new column equal to sum(n, na.rm=T). To add a conditional element to the sum operation, you can use the subset bracket [ ] syntax on the counts column.\n\nlinelist_agg %>% \n group_by(outcome) %>% \n summarise(\n total_cases = sum(n, na.rm=T),\n male_cases = sum(n[gender == \"m\"], na.rm=T),\n female_cases = sum(n[gender == \"f\"], na.rm=T))\n\n# A tibble: 2 × 4\n outcome total_cases male_cases female_cases\n <chr> <int> <int> <int>\n1 Death 2455 1228 1227\n2 Recover 1903 950 953\n\n\n\n\nacross() multiple columns\nYou can use summarise() across multiple columns using across(). This makes life easier when you want to calculate the same statistics for many columns. Place across() within summarise() and specify the following:\n\n.cols = as either a vector of column names c() or “tidyselect” helper functions (explained below).\n\n.fns = the function to perform (no parentheses) - you can provide multiple within a list().\n\nBelow, mean() is applied to several numeric columns. A vector of columns are named explicitly to .cols = and a single function mean is specified (no parentheses) to .fns =. Any additional arguments for the function (e.g. na.rm=TRUE) are provided after .fns =, separated by a comma.\nIt can be difficult to get the order of parentheses and commas correct when using across(). Remember that within across() you must include the columns, the functions, and any extra arguments needed for the functions.\n\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns\n .fns = mean, # function\n na.rm=T)) # extra arguments\n\n# A tibble: 3 × 5\n outcome age_years temp wt_kg ht_cm\n <chr> <dbl> <dbl> <dbl> <dbl>\n1 Death 15.9 38.6 52.6 125.\n2 Recover 16.1 38.6 52.5 125.\n3 <NA> 16.2 38.6 53.0 125.\n\n\nMultiple functions can be run at once. Below the functions mean and sd are provided to .fns = within a list(). You have the opportunity to provide character names (e.g. “mean” and “sd”) which are appended in the new column names.\n\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns\n .fns = list(\"mean\" = mean, \"sd\" = sd), # multiple functions \n na.rm=T)) # extra arguments\n\n# A tibble: 3 × 9\n outcome age_years_mean age_years_sd temp_mean temp_sd wt_kg_mean wt_kg_sd\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 Death 15.9 12.3 38.6 0.962 52.6 18.4\n2 Recover 16.1 13.0 38.6 0.997 52.5 18.6\n3 <NA> 16.2 12.8 38.6 0.976 53.0 18.9\n# ℹ 2 more variables: ht_cm_mean <dbl>, ht_cm_sd <dbl>\n\n\nHere are those “tidyselect” helper functions you can provide to .cols = to select columns:\n\neverything() - all other columns not mentioned.\n\nlast_col() - the last column.\n\nwhere() - applies a function to all columns and selects those which are TRUE.\n\nstarts_with() - matches to a specified prefix. Example: starts_with(\"date\").\nends_with() - matches to a specified suffix. Example: ends_with(\"_end\").\n\ncontains() - columns containing a character string. Example: contains(\"time\").\nmatches() - to apply a regular expression (regex). Example: contains(\"[pt]al\").\n\nnum_range() - matches a numerical range. Example: num_range(\"wk\", 1:5) would select columns with the prefix “wk” and the number 1:5 after.\nany_of() - matches if column is named. Useful if the name might not exist. Example: any_of(date_onset, date_death, cardiac_arrest).\n\nFor example, to return the mean of every numeric column use where() and provide the function as.numeric() (without parentheses). All this remains within the across() command.\n\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(\n .cols = where(is.numeric), # all numeric columns in the data frame\n .fns = mean,\n na.rm=T))\n\n# A tibble: 3 × 12\n outcome generation age age_years lon lat wt_kg ht_cm ct_blood temp\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 Death 16.7 15.9 15.9 -13.2 8.47 52.6 125. 21.3 38.6\n2 Recover 16.4 16.2 16.1 -13.2 8.47 52.5 125. 21.1 38.6\n3 <NA> 16.5 16.3 16.2 -13.2 8.47 53.0 125. 21.2 38.6\n# ℹ 2 more variables: bmi <dbl>, days_onset_hosp <dbl>\n\n\n\n\nPivot wider\nIf you prefer your table in “wide” format you can transform it using the tidyr pivot_wider() function. You will likely need to re-name the columns with rename(). For more information see the page on Pivoting data.\nThe example below begins with the “long” table age_by_outcome from the proportions section. We create it again and print, for clarity:\n\nage_by_outcome <- linelist %>% # begin with linelist\n group_by(outcome) %>% # group by outcome \n count(age_cat) %>% # group and count by age_cat, and then remove age_cat grouping\n mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group\n\n\n\n\n\n\n\nTo pivot wider, we create the new columns from the values in the existing column age_cat (by setting names_from = age_cat). We also specify that the new table values will come from the existing column n, with values_from = n. The columns not mentioned in our pivoting command (outcome) will remain unchanged on the far left side.\n\nage_by_outcome %>% \n select(-percent) %>% # keep only counts for simplicity\n pivot_wider(names_from = age_cat, values_from = n) \n\n# A tibble: 3 × 10\n# Groups: outcome [3]\n outcome `0-4` `5-9` `10-14` `15-19` `20-29` `30-49` `50-69` `70+` `NA`\n <chr> <int> <int> <int> <int> <int> <int> <int> <int> <int>\n1 Death 471 476 438 323 477 329 33 3 32\n2 Recover 364 391 303 251 367 238 38 3 28\n3 <NA> 260 228 200 169 229 187 24 NA 26\n\n\n\n\nTotal rows\nWhen summarise() operates on grouped data it does not automatically produce “total” statistics. Below, two approaches to adding a total row are presented:\n\njanitor’s adorn_totals()\nIf your table consists only of counts or proportions/percents that can be summed into a total, then you can add sum totals using janitor’s adorn_totals() as described in the section above. Note that this function can only sum the numeric columns - if you want to calculate other total summary statistics see the next approach with dplyr.\nBelow, linelist is grouped by gender and summarised into a table that described the number of cases with known outcome, deaths, and recovered. Piping the table to adorn_totals() adds a total row at the bottom reflecting the sum of each column. The further adorn_*() functions adjust the display as noted in the code.\n\nlinelist %>% \n group_by(gender) %>%\n summarise(\n known_outcome = sum(!is.na(outcome)), # Number of rows in group where outcome is not missing\n n_death = sum(outcome == \"Death\", na.rm=T), # Number of rows in group where outcome is Death\n n_recover = sum(outcome == \"Recover\", na.rm=T), # Number of rows in group where outcome is Recovered\n ) %>% \n adorn_totals() %>% # Adorn total row (sums of each numeric column)\n adorn_percentages(\"col\") %>% # Get column proportions\n adorn_pct_formatting() %>% # Convert proportions to percents\n adorn_ns(position = \"front\") # display % and counts (with counts in front)\n\n gender known_outcome n_death n_recover\n f 2,180 (47.8%) 1,227 (47.5%) 953 (48.1%)\n m 2,178 (47.7%) 1,228 (47.6%) 950 (47.9%)\n <NA> 207 (4.5%) 127 (4.9%) 80 (4.0%)\n Total 4,565 (100.0%) 2,582 (100.0%) 1,983 (100.0%)\n\n\n\n\nsummarise() on “total” data and then bind_rows()\nIf your table consists of summary statistics such as median(), mean(), etc, the adorn_totals() approach shown above will not be sufficient. Instead, to get summary statistics for the entire dataset you must calculate them with a separate summarise() command and then bind the results to the original grouped summary table. To do the binding you can use bind_rows() from dplyr s described in the Joining data page. Below is an example:\nYou can make a summary table of outcome by hospital with group_by() and summarise() like this:\n\nby_hospital <- linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>% # Remove cases with missing outcome or hospital\n group_by(hospital, outcome) %>% # Group data\n summarise( # Create new summary columns of indicators of interest\n N = n(), # Number of rows per hospital-outcome group \n ct_value = median(ct_blood, na.rm=T) # median CT value per group\n ) \n \nby_hospital # print table\n\n# A tibble: 10 × 4\n# Groups: hospital [5]\n hospital outcome N ct_value\n <chr> <chr> <int> <dbl>\n 1 Central Hospital Death 193 22\n 2 Central Hospital Recover 165 22\n 3 Military Hospital Death 399 21\n 4 Military Hospital Recover 309 22\n 5 Other Death 395 22\n 6 Other Recover 290 21\n 7 Port Hospital Death 785 22\n 8 Port Hospital Recover 579 21\n 9 St. Mark's Maternity Hospital (SMMH) Death 199 22\n10 St. Mark's Maternity Hospital (SMMH) Recover 126 22\n\n\nTo get the totals, run the same summarise() command but only group the data by outcome (not by hospital), like this:\n\ntotals <- linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>%\n group_by(outcome) %>% # Grouped only by outcome, not by hospital \n summarise(\n N = n(), # These statistics are now by outcome only \n ct_value = median(ct_blood, na.rm=T))\n\ntotals # print table\n\n# A tibble: 2 × 3\n outcome N ct_value\n <chr> <int> <dbl>\n1 Death 1971 22\n2 Recover 1469 22\n\n\nWe can bind these two data frames together. Note that by_hospital has 4 columns whereas totals has 3 columns. By using bind_rows(), the columns are combined by name, and any extra space is filled in with NA (e.g the column hospital values for the two new totals rows). After binding the rows, we convert these empty spaces to “Total” using replace_na() (see Cleaning data and core functions page).\n\ntable_long <- bind_rows(by_hospital, totals) %>% \n mutate(hospital = replace_na(hospital, \"Total\"))\n\nHere is the new table with “Total” rows at the bottom.\n\n\n\n\n\n\nThis table is in a “long” format, which may be what you want. Optionally, you can pivot this table wider to make it more readable. See the section on pivoting wider above, and the Pivoting data page. You can also add more columns, and arrange it nicely. This code is below.\n\ntable_long %>% \n \n # Pivot wider and format\n ########################\n mutate(hospital = replace_na(hospital, \"Total\")) %>% \n pivot_wider( # Pivot from long to wide\n values_from = c(ct_value, N), # new values are from ct and count columns\n names_from = outcome) %>% # new column names are from outcomes\n mutate( # Add new columns\n N_Known = N_Death + N_Recover, # number with known outcome\n Pct_Death = scales::percent(N_Death / N_Known, 0.1), # percent cases who died (to 1 decimal)\n Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)\n select( # Re-order columns\n hospital, N_Known, # Intro columns\n N_Recover, Pct_Recover, ct_value_Recover, # Recovered columns\n N_Death, Pct_Death, ct_value_Death) %>% # Death columns\n arrange(N_Known) # Arrange rows from lowest to highest (Total row at bottom)\n\n# A tibble: 6 × 8\n# Groups: hospital [6]\n hospital N_Known N_Recover Pct_Recover ct_value_Recover N_Death Pct_Death\n <chr> <int> <int> <chr> <dbl> <int> <chr> \n1 St. Mark's M… 325 126 38.8% 22 199 61.2% \n2 Central Hosp… 358 165 46.1% 22 193 53.9% \n3 Other 685 290 42.3% 21 395 57.7% \n4 Military Hos… 708 309 43.6% 22 399 56.4% \n5 Port Hospital 1364 579 42.4% 21 785 57.6% \n6 Total 3440 1469 42.7% 22 1971 57.3% \n# ℹ 1 more variable: ct_value_Death <dbl>\n\n\nAnd then you can print this nicely as an image - below is the output printed with flextable. You can read more in depth about this example and how to achieve this “pretty” table in the Tables for presentation page.\n\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22", "crumbs": [ "Analysis", "17  Descriptive tables" @@ -1572,7 +1572,7 @@ "href": "new_pages/tables_descriptive.html#tbl_gt", "title": "17  Descriptive tables", "section": "17.5 gtsummary package", - "text": "17.5 gtsummary package\nIf you want to print your summary statistics in a pretty, publication-ready graphic, you can use the gtsummary package and its function tbl_summary(). The code can seem complex at first, but the outputs look very nice and print to your RStudio Viewer panel as an HTML image. Read a vignette here.\nYou can also add the results of statistical tests to gtsummary tables. This process is described in the gtsummary section of the Simple statistical tests page.\nTo introduce tbl_summary() we will show the most basic behavior first, which actually produces a large and beautiful table. Then, we will examine in detail how to make adjustments and more tailored tables.\n\nSummary table\nThe default behavior of tbl_summary() is quite incredible - it takes the columns you provide and creates a summary table in one command. The function prints statistics appropriate to the column class: median and inter-quartile range (IQR) for numeric columns, and counts (%) for categorical columns. Missing values are converted to “Unknown”. Footnotes are added to the bottom to explain the statistics, while the total N is shown at the top.\n\nlinelist %>% \n select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only the columns of interest\n tbl_summary() # default\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,8881\n\n\n\n\nage_years\n13 (6, 23)\n\n\n    Unknown\n86\n\n\ngender\n\n\n\n\n    f\n2,807 (50%)\n\n\n    m\n2,803 (50%)\n\n\n    Unknown\n278\n\n\noutcome\n\n\n\n\n    Death\n2,582 (57%)\n\n\n    Recover\n1,983 (43%)\n\n\n    Unknown\n1,323\n\n\nfever\n4,549 (81%)\n\n\n    Unknown\n249\n\n\ntemp\n38.80 (38.20, 39.20)\n\n\n    Unknown\n149\n\n\nhospital\n\n\n\n\n    Central Hospital\n454 (7.7%)\n\n\n    Military Hospital\n896 (15%)\n\n\n    Missing\n1,469 (25%)\n\n\n    Other\n885 (15%)\n\n\n    Port Hospital\n1,762 (30%)\n\n\n    St. Mark's Maternity Hospital (SMMH)\n422 (7.2%)\n\n\n\n1 Median (IQR); n (%)\n\n\n\n\n\n\n\n\n\n\n\nAdjustments\nNow we will explain how the function works and how to make adjustments. The key arguments are detailed below:\nby =\nYou can stratify your table by a column (e.g. by outcome), creating a 2-way table.\nstatistic =\nUse an equations to specify which statistics to show and how to display them. There are two sides to the equation, separated by a tilde ~. On the right side, in quotes, is the statistical display desired, and on the left are the columns to which that display will apply.\n\nThe right side of the equation uses the syntax of str_glue() from stringr (see [Characters and Strings])(characters_strings.qmd), with the desired display string in quotes and the statistics themselves within curly brackets. You can include statistics like “n” (for counts), “N” (for denominator), “mean”, “median”, “sd”, “max”, “min”, percentiles as “p##” like “p25”, or percent of total as “p”. See ?tbl_summary for details.\n\nFor the left side of the equation, you can specify columns by name (e.g. age or c(age, gender)) or using helpers such as all_continuous(), all_categorical(), contains(), starts_with(), etc.\n\nA simple example of a statistic = equation might look like below, to only print the mean of column age_years:\n\nlinelist %>% \n select(age_years) %>% # keep only columns of interest \n tbl_summary( # create summary table\n statistic = age_years ~ \"{mean}\") # print mean of age\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,8881\n\n\n\n\nage_years\n16\n\n\n    Unknown\n86\n\n\n\n1 Mean\n\n\n\n\n\n\n\n\n\nA slightly more complex equation might look like \"({min}, {max})\", incorporating the max and min values within parentheses and separated by a comma:\n\nlinelist %>% \n select(age_years) %>% # keep only columns of interest \n tbl_summary( # create summary table\n statistic = age_years ~ \"({min}, {max})\") # print min and max of age\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,8881\n\n\n\n\nage_years\n(0, 84)\n\n\n    Unknown\n86\n\n\n\n1 (Range)\n\n\n\n\n\n\n\n\n\nYou can also differentiate syntax for separate columns or types of columns. In the more complex example below, the value provided to statistc = is a list indicating that for all continuous columns the table should print mean with standard deviation in parentheses, while for all categorical columns it should print the n, denominator, and percent.\ndigits =\nAdjust the digits and rounding. Optionally, this can be specified to be for continuous columns only (as below).\nlabel =\nAdjust how the column name should be displayed. Provide the column name and its desired label separated by a tilde. The default is the column name.\nmissing_text =\nAdjust how missing values are displayed. The default is “Unknown”.\ntype =\nThis is used to adjust how many levels of the statistics are shown. The syntax is similar to statistic = in that you provide an equation with columns on the left and a value on the right. Two common scenarios include:\n\ntype = all_categorical() ~ \"categorical\" Forces dichotomous columns (e.g. fever yes/no) to show all levels instead of only the “yes” row\n\ntype = all_continuous() ~ \"continuous2\" Allows multi-line statistics per variable, as shown in a later section\n\nIn the example below, each of these arguments is used to modify the original summary table:\n\nlinelist %>% \n select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only columns of interest\n tbl_summary( \n by = outcome, # stratify entire table by outcome\n statistic = list(all_continuous() ~ \"{mean} ({sd})\", # stats and format for continuous columns\n all_categorical() ~ \"{n} / {N} ({p}%)\"), # stats and format for categorical columns\n digits = all_continuous() ~ 1, # rounding for continuous columns\n type = all_categorical() ~ \"categorical\", # force all categorical levels to display\n label = list( # display labels for column names\n outcome ~ \"Outcome\", \n age_years ~ \"Age (years)\",\n gender ~ \"Gender\",\n temp ~ \"Temperature\",\n hospital ~ \"Hospital\"),\n missing_text = \"Missing\" # how missing values should display\n )\n\n1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath, N = 2,5821\nRecover, N = 1,9831\n\n\n\n\nAge (years)\n15.9 (12.3)\n16.1 (13.0)\n\n\n    Missing\n32\n28\n\n\nGender\n\n\n\n\n\n\n    f\n1,227 / 2,455 (50%)\n953 / 1,903 (50%)\n\n\n    m\n1,228 / 2,455 (50%)\n950 / 1,903 (50%)\n\n\n    Missing\n127\n80\n\n\nfever\n\n\n\n\n\n\n    no\n458 / 2,460 (19%)\n361 / 1,904 (19%)\n\n\n    yes\n2,002 / 2,460 (81%)\n1,543 / 1,904 (81%)\n\n\n    Missing\n122\n79\n\n\nTemperature\n38.6 (1.0)\n38.6 (1.0)\n\n\n    Missing\n60\n55\n\n\nHospital\n\n\n\n\n\n\n    Central Hospital\n193 / 2,582 (7.5%)\n165 / 1,983 (8.3%)\n\n\n    Military Hospital\n399 / 2,582 (15%)\n309 / 1,983 (16%)\n\n\n    Missing\n611 / 2,582 (24%)\n514 / 1,983 (26%)\n\n\n    Other\n395 / 2,582 (15%)\n290 / 1,983 (15%)\n\n\n    Port Hospital\n785 / 2,582 (30%)\n579 / 1,983 (29%)\n\n\n    St. Mark's Maternity Hospital (SMMH)\n199 / 2,582 (7.7%)\n126 / 1,983 (6.4%)\n\n\n\n1 Mean (SD); n / N (%)\n\n\n\n\n\n\n\n\n\n\n\nMulti-line stats for continuous variables\nIf you want to print multiple lines of statistics for continuous variables, you can indicate this by setting the type = to “continuous2”. You can combine all of the previously shown elements in one table by choosing which statistics you want to show. To do this you need to tell the function that you want to get a table back by entering the type as “continuous2”. The number of missing values is shown as “Unknown”.\n\nlinelist %>% \n select(age_years, temp) %>% # keep only columns of interest\n tbl_summary( # create summary table\n type = all_continuous() ~ \"continuous2\", # indicate that you want to print multiple statistics \n statistic = all_continuous() ~ c(\n \"{mean} ({sd})\", # line 1: mean and SD\n \"{median} ({p25}, {p75})\", # line 2: median and IQR\n \"{min}, {max}\") # line 3: min and max\n )\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,888\n\n\n\n\nage_years\n\n\n\n\n    Mean (SD)\n16 (13)\n\n\n    Median (IQR)\n13 (6, 23)\n\n\n    Range\n0, 84\n\n\n    Unknown\n86\n\n\ntemp\n\n\n\n\n    Mean (SD)\n38.56 (0.98)\n\n\n    Median (IQR)\n38.80 (38.20, 39.20)\n\n\n    Range\n35.20, 40.80\n\n\n    Unknown\n149\n\n\n\n\n\n\n\n\nThere are many other ways to modify these tables, including adding p-values, adjusting color and headings, etc. Many of these are described in the documentation (enter ?tbl_summary in Console), and some are given in the section on statistical tests.", + "text": "17.5 gtsummary package\nIf you want to print your summary statistics in a pretty, publication-ready graphic, you can use the gtsummary package and its function tbl_summary(). The code can seem complex at first, but the outputs look very nice and print to your RStudio Viewer panel as an HTML image. Read a vignette here.\nYou can also add the results of statistical tests to gtsummary tables. This process is described in the gtsummary section of the Simple statistical tests page.\nTo introduce tbl_summary() we will show the most basic behavior first, which actually produces a large and beautiful table. Then, we will examine in detail how to make adjustments and more tailored tables.\n\nSummary table\nThe default behavior of tbl_summary() is quite incredible - it takes the columns you provide and creates a summary table in one command. The function prints statistics appropriate to the column class: median and inter-quartile range (IQR) for numeric columns, and counts (%) for categorical columns. Missing values are converted to “Unknown”. Footnotes are added to the bottom to explain the statistics, while the total N is shown at the top.\n\nlinelist %>% \n select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only the columns of interest\n tbl_summary() # default\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,888\n1\n\n\n\n\nage_years\n13 (6, 23)\n\n\n    Unknown\n86\n\n\ngender\n\n\n\n\n    f\n2,807 (50%)\n\n\n    m\n2,803 (50%)\n\n\n    Unknown\n278\n\n\noutcome\n\n\n\n\n    Death\n2,582 (57%)\n\n\n    Recover\n1,983 (43%)\n\n\n    Unknown\n1,323\n\n\nfever\n4,549 (81%)\n\n\n    Unknown\n249\n\n\ntemp\n38.80 (38.20, 39.20)\n\n\n    Unknown\n149\n\n\nhospital\n\n\n\n\n    Central Hospital\n454 (7.7%)\n\n\n    Military Hospital\n896 (15%)\n\n\n    Missing\n1,469 (25%)\n\n\n    Other\n885 (15%)\n\n\n    Port Hospital\n1,762 (30%)\n\n\n    St. Mark's Maternity Hospital (SMMH)\n422 (7.2%)\n\n\n\n1\nMedian (Q1, Q3); n (%)\n\n\n\n\n\n\n\n\n\n\nAdjustments\nNow we will explain how the function works and how to make adjustments. The key arguments are detailed below:\nby =\nYou can stratify your table by a column (e.g. by outcome), creating a 2-way table.\nstatistic =\nUse an equations to specify which statistics to show and how to display them. There are two sides to the equation, separated by a tilde ~. On the right side, in quotes, is the statistical display desired, and on the left are the columns to which that display will apply.\n\nThe right side of the equation uses the syntax of str_glue() from stringr (see [Characters and Strings])(characters_strings.qmd), with the desired display string in quotes and the statistics themselves within curly brackets. You can include statistics like “n” (for counts), “N” (for denominator), “mean”, “median”, “sd”, “max”, “min”, percentiles as “p##” like “p25”, or percent of total as “p”. See ?tbl_summary for details.\n\nFor the left side of the equation, you can specify columns by name (e.g. age or c(age, gender)) or using helpers such as all_continuous(), all_categorical(), contains(), starts_with(), etc.\n\nA simple example of a statistic = equation might look like below, to only print the mean of column age_years:\n\nlinelist %>% \n select(age_years) %>% # keep only columns of interest \n tbl_summary( # create summary table\n statistic = age_years ~ \"{mean}\") # print mean of age\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,888\n1\n\n\n\n\nage_years\n16\n\n\n    Unknown\n86\n\n\n\n1\nMean\n\n\n\n\n\n\n\n\nA slightly more complex equation might look like \"({min}, {max})\", incorporating the max and min values within parentheses and separated by a comma:\n\nlinelist %>% \n select(age_years) %>% # keep only columns of interest \n tbl_summary( # create summary table\n statistic = age_years ~ \"({min}, {max})\") # print min and max of age\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,888\n1\n\n\n\n\nage_years\n(0, 84)\n\n\n    Unknown\n86\n\n\n\n1\n(Min, Max)\n\n\n\n\n\n\n\n\nYou can also differentiate syntax for separate columns or types of columns. In the more complex example below, the value provided to statistc = is a list indicating that for all continuous columns the table should print mean with standard deviation in parentheses, while for all categorical columns it should print the n, denominator, and percent.\ndigits =\nAdjust the digits and rounding. Optionally, this can be specified to be for continuous columns only (as below).\nlabel =\nAdjust how the column name should be displayed. Provide the column name and its desired label separated by a tilde. The default is the column name.\nmissing_text =\nAdjust how missing values are displayed. The default is “Unknown”.\ntype =\nThis is used to adjust how many levels of the statistics are shown. The syntax is similar to statistic = in that you provide an equation with columns on the left and a value on the right. Two common scenarios include:\n\ntype = all_categorical() ~ \"categorical\" Forces dichotomous columns (e.g. fever yes/no) to show all levels instead of only the “yes” row.\ntype = all_continuous() ~ \"continuous2\" Allows multi-line statistics per variable, as shown in a later section.\n\nIn the example below, each of these arguments is used to modify the original summary table:\n\nlinelist %>% \n select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only columns of interest\n tbl_summary( \n by = outcome, # stratify entire table by outcome\n statistic = list(all_continuous() ~ \"{mean} ({sd})\", # stats and format for continuous columns\n all_categorical() ~ \"{n} / {N} ({p}%)\"), # stats and format for categorical columns\n digits = all_continuous() ~ 1, # rounding for continuous columns\n type = all_categorical() ~ \"categorical\", # force all categorical levels to display\n label = list( # display labels for column names\n age_years ~ \"Age (years)\",\n gender ~ \"Gender\",\n temp ~ \"Temperature\",\n hospital ~ \"Hospital\"),\n missing_text = \"Missing\" # how missing values should display\n )\n\n1323 missing rows in the \"outcome\" column have been removed.\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath\nN = 2,582\n1\nRecover\nN = 1,983\n1\n\n\n\n\nAge (years)\n15.9 (12.3)\n16.1 (13.0)\n\n\n    Missing\n32\n28\n\n\nGender\n\n\n\n\n\n\n    f\n1,227 / 2455 (50%)\n953 / 1903 (50%)\n\n\n    m\n1,228 / 2455 (50%)\n950 / 1903 (50%)\n\n\n    Missing\n127\n80\n\n\nfever\n\n\n\n\n\n\n    no\n458 / 2460 (19%)\n361 / 1904 (19%)\n\n\n    yes\n2,002 / 2460 (81%)\n1,543 / 1904 (81%)\n\n\n    Missing\n122\n79\n\n\nTemperature\n38.6 (1.0)\n38.6 (1.0)\n\n\n    Missing\n60\n55\n\n\nHospital\n\n\n\n\n\n\n    Central Hospital\n193 / 2582 (7.5%)\n165 / 1983 (8.3%)\n\n\n    Military Hospital\n399 / 2582 (15%)\n309 / 1983 (16%)\n\n\n    Missing\n611 / 2582 (24%)\n514 / 1983 (26%)\n\n\n    Other\n395 / 2582 (15%)\n290 / 1983 (15%)\n\n\n    Port Hospital\n785 / 2582 (30%)\n579 / 1983 (29%)\n\n\n    St. Mark's Maternity Hospital (SMMH)\n199 / 2582 (7.7%)\n126 / 1983 (6.4%)\n\n\n\n1\nMean (SD); n / N (%)\n\n\n\n\n\n\n\n\n\n\nMulti-line stats for continuous variables\nIf you want to print multiple lines of statistics for continuous variables, you can indicate this by setting the type = to “continuous2”. You can combine all of the previously shown elements in one table by choosing which statistics you want to show. To do this you need to tell the function that you want to get a table back by entering the type as “continuous2”. The number of missing values is shown as “Unknown”.\n\nlinelist %>% \n select(age_years, temp) %>% # keep only columns of interest\n tbl_summary( # create summary table\n type = all_continuous() ~ \"continuous2\", # indicate that you want to print multiple statistics \n statistic = all_continuous() ~ c(\n \"{mean} ({sd})\", # line 1: mean and SD\n \"{median} ({p25}, {p75})\", # line 2: median and IQR\n \"{min}, {max}\") # line 3: min and max\n )\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN = 5,888\n\n\n\n\nage_years\n\n\n\n\n    Mean (SD)\n16 (13)\n\n\n    Median (Q1, Q3)\n13 (6, 23)\n\n\n    Min, Max\n0, 84\n\n\n    Unknown\n86\n\n\ntemp\n\n\n\n\n    Mean (SD)\n38.56 (0.98)\n\n\n    Median (Q1, Q3)\n38.80 (38.20, 39.20)\n\n\n    Min, Max\n35.20, 40.80\n\n\n    Unknown\n149\n\n\n\n\n\n\n\n\n\n17.5.1 tbl_wide_summary()\nYou may also want to display your results in wide format, rather than long. To do so in gtsummary you can use the function tbl_wide_summary().\n\nlinelist %>% \n select(age_years, temp) %>%\n tbl_wide_summary()\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nMedian\nQ1, Q3\n\n\n\n\nage_years\n13\n6, 23\n\n\ntemp\n38.80\n38.20, 39.20\n\n\n\n\n\n\n\nThere are many other ways to modify these tables, including adding p-values, adjusting color and headings, etc. Many of these are described in the documentation (enter ?tbl_summary in Console), and some are given in the section on statistical tests.", "crumbs": [ "Analysis", "17  Descriptive tables" @@ -1583,7 +1583,7 @@ "href": "new_pages/tables_descriptive.html#base-r", "title": "17  Descriptive tables", "section": "17.6 base R", - "text": "17.6 base R\nYou can use the function table() to tabulate and cross-tabulate columns. Unlike the options above, you must specify the dataframe each time you reference a column name, as shown below.\nCAUTION: NA (missing) values will not be tabulated unless you include the argument useNA = \"always\" (which could also be set to “no” or “ifany”).\nTIP: You can use the %$% from magrittr to remove the need for repeating data frame calls within base functions. For example the below could be written linelist %$% table(outcome, useNA = \"always\") \n\ntable(linelist$outcome, useNA = \"always\")\n\n\n Death Recover <NA> \n 2582 1983 1323 \n\n\nMultiple columns can be cross-tabulated by listing them one after the other, separated by commas. Optionally, you can assign each column a “name” like Outcome = linelist$outcome.\n\nage_by_outcome <- table(linelist$age_cat, linelist$outcome, useNA = \"always\") # save table as object\nage_by_outcome # print table\n\n \n Death Recover <NA>\n 0-4 471 364 260\n 5-9 476 391 228\n 10-14 438 303 200\n 15-19 323 251 169\n 20-29 477 367 229\n 30-49 329 238 187\n 50-69 33 38 24\n 70+ 3 3 0\n <NA> 32 28 26\n\n\n\nProportions\nTo return proportions, passing the above table to the function prop.table(). Use the margins = argument to specify whether you want the proportions to be of rows (1), of columns (2), or of the whole table (3). For clarity, we pipe the table to the round() function from base R, specifying 2 digits.\n\n# get proportions of table defined above, by rows, rounded\nprop.table(age_by_outcome, 1) %>% round(2)\n\n \n Death Recover <NA>\n 0-4 0.43 0.33 0.24\n 5-9 0.43 0.36 0.21\n 10-14 0.47 0.32 0.21\n 15-19 0.43 0.34 0.23\n 20-29 0.44 0.34 0.21\n 30-49 0.44 0.32 0.25\n 50-69 0.35 0.40 0.25\n 70+ 0.50 0.50 0.00\n <NA> 0.37 0.33 0.30\n\n\n\n\nTotals\nTo add row and column totals, pass the table to addmargins(). This works for both counts and proportions.\n\naddmargins(age_by_outcome)\n\n \n Death Recover <NA> Sum\n 0-4 471 364 260 1095\n 5-9 476 391 228 1095\n 10-14 438 303 200 941\n 15-19 323 251 169 743\n 20-29 477 367 229 1073\n 30-49 329 238 187 754\n 50-69 33 38 24 95\n 70+ 3 3 0 6\n <NA> 32 28 26 86\n Sum 2582 1983 1323 5888\n\n\n\n\nConvert to data frame\nConverting a table() object directly to a data frame is not straight-forward. One approach is demonstrated below:\n\nCreate the table, without using useNA = \"always\". Instead convert NA values to “(Missing)” with fct_explicit_na() from forcats.\n\nAdd totals (optional) by piping to addmargins()\n\nPipe to the base R function as.data.frame.matrix()\n\nPipe the table to the tibble function rownames_to_column(), specifying the name for the first column\n\nPrint, View, or export as desired. In this example we use flextable() from package flextable as described in the Tables for presentation page. This will print to the RStudio viewer pane as a pretty HTML image.\n\n\ntable(fct_explicit_na(linelist$age_cat), fct_explicit_na(linelist$outcome)) %>% \n addmargins() %>% \n as.data.frame.matrix() %>% \n tibble::rownames_to_column(var = \"Age Category\") %>% \n flextable::flextable()\n\nAge CategoryDeathRecover(Missing)Sum0-44713642601,0955-94763912281,09510-1443830320094115-1932325116974320-294773672291,07330-4932923818775450-693338249570+3306(Missing)32282686Sum2,5821,9831,3235,888", + "text": "17.6 base R\nYou can use the function table() to tabulate and cross-tabulate columns. Unlike the options above, you must specify the dataframe each time you reference a column name, as shown below.\nCAUTION: NA (missing) values will not be tabulated unless you include the argument useNA = \"always\" (which could also be set to “no” or “ifany”).\nTIP: You can use the %$% from magrittr to remove the need for repeating data frame calls within base functions. For example the below could be written linelist %$% table(outcome, useNA = \"always\") \n\ntable(linelist$outcome, useNA = \"always\")\n\n\n Death Recover <NA> \n 2582 1983 1323 \n\n\nMultiple columns can be cross-tabulated by listing them one after the other, separated by commas. Optionally, you can assign each column a “name” like Outcome = linelist$outcome.\n\nage_by_outcome <- table(linelist$age_cat, linelist$outcome, useNA = \"always\") # save table as object\nage_by_outcome # print table\n\n \n Death Recover <NA>\n 0-4 471 364 260\n 5-9 476 391 228\n 10-14 438 303 200\n 15-19 323 251 169\n 20-29 477 367 229\n 30-49 329 238 187\n 50-69 33 38 24\n 70+ 3 3 0\n <NA> 32 28 26\n\n\n\nProportions\nTo return proportions, passing the above table to the function prop.table(). Use the margins = argument to specify whether you want the proportions to be of rows (1), of columns (2), or of the whole table (3). For clarity, we pipe the table to the round() function from base R, specifying 2 digits.\n\n# get proportions of table defined above, by rows, rounded\nprop.table(age_by_outcome, 1) %>% round(2)\n\n \n Death Recover <NA>\n 0-4 0.43 0.33 0.24\n 5-9 0.43 0.36 0.21\n 10-14 0.47 0.32 0.21\n 15-19 0.43 0.34 0.23\n 20-29 0.44 0.34 0.21\n 30-49 0.44 0.32 0.25\n 50-69 0.35 0.40 0.25\n 70+ 0.50 0.50 0.00\n <NA> 0.37 0.33 0.30\n\n\n\n\nTotals\nTo add row and column totals, pass the table to addmargins(). This works for both counts and proportions.\n\naddmargins(age_by_outcome)\n\n \n Death Recover <NA> Sum\n 0-4 471 364 260 1095\n 5-9 476 391 228 1095\n 10-14 438 303 200 941\n 15-19 323 251 169 743\n 20-29 477 367 229 1073\n 30-49 329 238 187 754\n 50-69 33 38 24 95\n 70+ 3 3 0 6\n <NA> 32 28 26 86\n Sum 2582 1983 1323 5888\n\n\n\n\nConvert to data frame\nConverting a table() object directly to a data frame is not straight-forward. One approach is demonstrated below:\n\nCreate the table, without using useNA = \"always\". Instead convert NA values to “(Missing)” with fct_explicit_na() from forcats.\n\nAdd totals (optional) by piping to addmargins().\n\nPipe to the base R function as.data.frame.matrix().\n\nPipe the table to the tibble function rownames_to_column(), specifying the name for the first column.\nPrint, View, or export as desired. In this example we use flextable() from package flextable as described in the Tables for presentation page. This will print to the RStudio viewer pane as a pretty HTML image.\n\n\ntable(fct_explicit_na(linelist$age_cat), fct_explicit_na(linelist$outcome)) %>% \n addmargins() %>% \n as.data.frame.matrix() %>% \n tibble::rownames_to_column(var = \"Age Category\") %>% \n flextable::flextable()\n\nAge CategoryDeathRecover(Missing)Sum0-44713642601,0955-94763912281,09510-1443830320094115-1932325116974320-294773672291,07330-4932923818775450-693338249570+3306(Missing)32282686Sum2,5821,9831,3235,888", "crumbs": [ "Analysis", "17  Descriptive tables" @@ -1649,7 +1649,7 @@ "href": "new_pages/stat_tests.html#stats_gt", "title": "18  Simple statistical tests", "section": "18.4 gtsummary package", - "text": "18.4 gtsummary package\nUse gtsummary if you are looking to add the results of a statistical test to a pretty table that was created with this package (as described in the gtsummary section of the Descriptive tables page).\nPerforming statistical tests of comparison with tbl_summary is done by adding the add_p function to a table and specifying which test to use. It is possible to get p-values corrected for multiple testing by using the add_q function. Run ?tbl_summary for details.\n\nChi-squared test\nCompare the proportions of a categorical variable in two groups. The default statistical test for add_p() when applied to a categorical variable is to perform a chi-squared test of independence with continuity correction, but if any expected call count is below 5 then a Fisher’s exact test is used.\n\nlinelist %>% \n select(gender, outcome) %>% # keep variables of interest\n tbl_summary(by = outcome) %>% # produce summary table and specify grouping variable\n add_p() # specify what test to perform\n\n1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath, N = 2,5821\nRecover, N = 1,9831\np-value2\n\n\n\n\ngender\n\n\n\n\n>0.9\n\n\n    f\n1,227 (50%)\n953 (50%)\n\n\n\n\n    m\n1,228 (50%)\n950 (50%)\n\n\n\n\n    Unknown\n127\n80\n\n\n\n\n\n1 n (%)\n\n\n2 Pearson’s Chi-squared test\n\n\n\n\n\n\n\n\n\n\n\nT-tests\nCompare the difference in means for a continuous variable in two groups. For example, compare the mean age by patient outcome.\n\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{mean} ({sd})\", # specify what statistics to show\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"t.test\") # specify what tests to perform\n\n1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath, N = 2,5821\nRecover, N = 1,9831\np-value2\n\n\n\n\nage_years\n16 (12)\n16 (13)\n0.6\n\n\n    Unknown\n32\n28\n\n\n\n\n\n1 Mean (SD)\n\n\n2 Welch Two Sample t-test\n\n\n\n\n\n\n\n\n\n\n\nWilcoxon rank sum test\nCompare the distribution of a continuous variable in two groups. The default is to use the Wilcoxon rank sum test and the median (IQR) when comparing two groups. However for non-normally distributed data or comparing multiple groups, the Kruskal-wallis test is more appropriate.\n\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{median} ({p25}, {p75})\", # specify what statistic to show (this is default so could remove)\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"wilcox.test\") # specify what test to perform (default so could leave brackets empty)\n\n1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath, N = 2,5821\nRecover, N = 1,9831\np-value2\n\n\n\n\nage_years\n13 (6, 23)\n13 (6, 23)\n0.8\n\n\n    Unknown\n32\n28\n\n\n\n\n\n1 Median (IQR)\n\n\n2 Wilcoxon rank sum test\n\n\n\n\n\n\n\n\n\n\n\nKruskal-wallis test\nCompare the distribution of a continuous variable in two or more groups, regardless of whether the data is normally distributed.\n\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{median} ({p25}, {p75})\", # specify what statistic to show (default, so could remove)\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"kruskal.test\") # specify what test to perform\n\n1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath, N = 2,5821\nRecover, N = 1,9831\np-value2\n\n\n\n\nage_years\n13 (6, 23)\n13 (6, 23)\n0.8\n\n\n    Unknown\n32\n28\n\n\n\n\n\n1 Median (IQR)\n\n\n2 Kruskal-Wallis rank sum test", + "text": "18.4 gtsummary package\nUse gtsummary if you are looking to add the results of a statistical test to a pretty table that was created with this package (as described in the gtsummary section of the Descriptive tables page).\nPerforming statistical tests of comparison with tbl_summary is done by adding the add_p function to a table and specifying which test to use. It is possible to get p-values corrected for multiple testing by using the add_q function. Run ?tbl_summary for details.\n\nChi-squared test\nCompare the proportions of a categorical variable in two groups. The default statistical test for add_p() when applied to a categorical variable is to perform a chi-squared test of independence with continuity correction, but if any expected call count is below 5 then a Fisher’s exact test is used.\n\nlinelist %>% \n select(gender, outcome) %>% # keep variables of interest\n tbl_summary(by = outcome) %>% # produce summary table and specify grouping variable\n add_p() # specify what test to perform\n\n1323 missing rows in the \"outcome\" column have been removed.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath\nN = 2,582\n1\nRecover\nN = 1,983\n1\np-value\n2\n\n\n\n\ngender\n\n\n\n\n>0.9\n\n\n    f\n1,227 (50%)\n953 (50%)\n\n\n\n\n    m\n1,228 (50%)\n950 (50%)\n\n\n\n\n    Unknown\n127\n80\n\n\n\n\n\n1\nn (%)\n\n\n2\nPearson’s Chi-squared test\n\n\n\n\n\n\n\n\n\n\nT-tests\nCompare the difference in means for a continuous variable in two groups. For example, compare the mean age by patient outcome.\n\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{mean} ({sd})\", # specify what statistics to show\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"t.test\") # specify what tests to perform\n\n1323 missing rows in the \"outcome\" column have been removed.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath\nN = 2,582\n1\nRecover\nN = 1,983\n1\np-value\n2\n\n\n\n\nage_years\n16 (12)\n16 (13)\n0.6\n\n\n    Unknown\n32\n28\n\n\n\n\n\n1\nMean (SD)\n\n\n2\nWelch Two Sample t-test\n\n\n\n\n\n\n\n\n\n\nWilcoxon rank sum test\nCompare the distribution of a continuous variable in two groups. The default is to use the Wilcoxon rank sum test and the median (IQR) when comparing two groups. However for non-normally distributed data or comparing multiple groups, the Kruskal-wallis test is more appropriate.\n\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{median} ({p25}, {p75})\", # specify what statistic to show (this is default so could remove)\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"wilcox.test\") # specify what test to perform (default so could leave brackets empty)\n\n1323 missing rows in the \"outcome\" column have been removed.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath\nN = 2,582\n1\nRecover\nN = 1,983\n1\np-value\n2\n\n\n\n\nage_years\n13 (6, 23)\n13 (6, 23)\n0.8\n\n\n    Unknown\n32\n28\n\n\n\n\n\n1\nMedian (Q1, Q3)\n\n\n2\nWilcoxon rank sum test\n\n\n\n\n\n\n\n\n\n\nKruskal-wallis test\nCompare the distribution of a continuous variable in two or more groups, regardless of whether the data is normally distributed.\n\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{median} ({p25}, {p75})\", # specify what statistic to show (default, so could remove)\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"kruskal.test\") # specify what test to perform\n\n1323 missing rows in the \"outcome\" column have been removed.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nDeath\nN = 2,582\n1\nRecover\nN = 1,983\n1\np-value\n2\n\n\n\n\nage_years\n13 (6, 23)\n13 (6, 23)\n0.8\n\n\n    Unknown\n32\n28\n\n\n\n\n\n1\nMedian (Q1, Q3)\n\n\n2\nKruskal-Wallis rank sum test", "crumbs": [ "Analysis", "18  Simple statistical tests" @@ -1671,7 +1671,7 @@ "href": "new_pages/stat_tests.html#resources", "title": "18  Simple statistical tests", "section": "18.6 Resources", - "text": "18.6 Resources\nMuch of the information in this page is adapted from these resources and vignettes online:\ngtsummary dplyr corrr sthda correlation", + "text": "18.6 Resources\nMuch of the information in this page is adapted from these resources and vignettes online:\ngtsummary\ndplyr\ncorrr\nsthda correlation\nResources for statistical theory\nCausal Inference: What If\nDiscovering statistics", "crumbs": [ "Analysis", "18  Simple statistical tests" @@ -1682,7 +1682,7 @@ "href": "new_pages/regression.html", "title": "19  Univariate and multivariable regression", "section": "", - "text": "19.1 Preparation", + "text": "Preparation", "crumbs": [ "Analysis", "19  Univariate and multivariable regression" @@ -1693,7 +1693,7 @@ "href": "new_pages/regression.html#preparation", "title": "19  Univariate and multivariable regression", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n tidyverse, # data management + ggplot2 graphics, \n stringr, # manipulate text strings \n purrr, # loop over objects in a tidy way\n gtsummary, # summary statistics and tests \n broom, # tidy up results from regressions\n lmtest, # likelihood-ratio tests\n parameters, # alternative to tidy up results from regressions\n see # alternative to visualise forest plots\n )\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\nClean data\n\nStore explanatory variables\nWe store the names of the explanatory columns as a character vector. This will be referenced later.\n\n## define variables of interest \nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n\n\nConvert to 1’s and 0’s\nBelow we convert the explanatory columns from “yes”/“no”, “m”/“f”, and “dead”/“alive” to 1 / 0, to cooperate with the expectations of logistic regression models. To do this efficiently, used across() from dplyr to transform multiple columns at one time. The function we apply to each column is case_when() (also dplyr) which applies logic to convert specified values to 1’s and 0’s. See sections on across() and case_when() in the Cleaning data and core functions page).\nNote: the “.” below represents the column that is being processed by across() at that moment.\n\n## convert dichotomous variables to 0/1 \nlinelist <- linelist %>% \n mutate(across( \n .cols = all_of(c(explanatory_vars, \"outcome\")), ## for each column listed and \"outcome\"\n .fns = ~case_when( \n . %in% c(\"m\", \"yes\", \"Death\") ~ 1, ## recode male, yes and death to 1\n . %in% c(\"f\", \"no\", \"Recover\") ~ 0, ## female, no and recover to 0\n TRUE ~ NA_real_) ## otherwise set to missing\n )\n )\n\n\n\nDrop rows with missing values\nTo drop rows with missing values, can use the tidyr function drop_na(). However, we only want to do this for rows that are missing values in the columns of interest.\nThe first thing we must to is make sure our explanatory_vars vector includes the column age (age would have produced an error in the previous case_when() operation, which was only for dichotomous variables). Then we pipe the linelist to drop_na() to remove any rows with missing values in the outcome column or any of the explanatory_vars columns.\nBefore running the code, the number of rows in the linelist is nrow(linelist).\n\n## add in age_category to the explanatory vars \nexplanatory_vars <- c(explanatory_vars, \"age_cat\")\n\n## drop rows with missing information for variables of interest \nlinelist <- linelist %>% \n drop_na(any_of(c(\"outcome\", explanatory_vars)))\n\nThe number of rows remaining in linelist is nrow(linelist).", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n tidyverse, # data management + ggplot2 graphics, \n stringr, # manipulate text strings \n purrr, # loop over objects in a tidy way\n gtsummary, # summary statistics and tests \n broom, # tidy up results from regressions\n lmtest, # likelihood-ratio tests\n parameters, # alternative to tidy up results from regressions\n see # alternative to visualise forest plots\n )\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\nClean data\n\nStore explanatory variables\nWe store the names of the explanatory columns as a character vector. This will be referenced later.\n\n## define variables of interest \nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n\n\nConvert to 1’s and 0’s\nBelow we convert the explanatory columns from “yes”/“no”, “m”/“f”, and “dead”/“alive” to 1 / 0, to cooperate with the expectations of logistic regression models. To do this efficiently, used across() from dplyr to transform multiple columns at one time. The function we apply to each column is case_when() (also dplyr) which applies logic to convert specified values to 1’s and 0’s. See sections on across() and case_when() in the Cleaning data and core functions page.\nNote: the “.” below represents the column that is being processed by across() at that moment.\n\n## convert dichotomous variables to 0/1 \nlinelist <- linelist %>% \n mutate(across( \n .cols = all_of(c(explanatory_vars, \"outcome\")), ## for each column listed and \"outcome\"\n .fns = ~case_when( \n . %in% c(\"m\", \"yes\", \"Death\") ~ 1, ## recode male, yes and death to 1\n . %in% c(\"f\", \"no\", \"Recover\") ~ 0, ## female, no and recover to 0\n TRUE ~ NA_real_) ## otherwise set to missing\n )\n )\n\n\n\nDrop rows with missing values\nTo drop rows with missing values, can use the tidyr function drop_na(). However, we only want to do this for rows that are missing values in the columns of interest.\nThe first thing we must to is make sure our explanatory_vars vector includes the column age (age would have produced an error in the previous case_when() operation, which was only for dichotomous variables). Then we pipe the linelist to drop_na() to remove any rows with missing values in the outcome column or any of the explanatory_vars columns.\nBefore running the code, the number of rows in the linelist is nrow(linelist).\n\n## add in age_category to the explanatory vars \nexplanatory_vars <- c(explanatory_vars, \"age_cat\")\n\n## drop rows with missing information for variables of interest \nlinelist <- linelist %>% \n drop_na(any_of(c(\"outcome\", explanatory_vars)))\n\nThe number of rows remaining in linelist is nrow(linelist).", "crumbs": [ "Analysis", "19  Univariate and multivariable regression" @@ -1703,8 +1703,8 @@ "objectID": "new_pages/regression.html#univariate", "href": "new_pages/regression.html#univariate", "title": "19  Univariate and multivariable regression", - "section": "19.2 Univariate", - "text": "19.2 Univariate\nJust like in the page on Descriptive tables, your use case will determine which R package you use. We present two options for doing univariate analysis:\n\nUse functions available in base R to quickly print results to the console. Use the broom package to tidy up the outputs.\n\nUse the gtsummary package to model and get publication-ready outputs\n\n\n\nbase R\n\nLinear regression\nThe base R function lm() perform linear regression, assessing the relationship between numeric response and explanatory variables that are assumed to have a linear relationship.\nProvide the equation as a formula, with the response and explanatory column names separated by a tilde ~. Also, specify the dataset to data =. Define the model results as an R object, to use later.\n\nlm_results <- lm(ht_cm ~ age, data = linelist)\n\nYou can then run summary() on the model results to see the coefficients (Estimates), P-value, residuals, and other measures.\n\nsummary(lm_results)\n\n\nCall:\nlm(formula = ht_cm ~ age, data = linelist)\n\nResiduals:\n Min 1Q Median 3Q Max \n-128.579 -15.854 1.177 15.887 175.483 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 69.9051 0.5979 116.9 <2e-16 ***\nage 3.4354 0.0293 117.2 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 23.75 on 4165 degrees of freedom\nMultiple R-squared: 0.7675, Adjusted R-squared: 0.7674 \nF-statistic: 1.375e+04 on 1 and 4165 DF, p-value: < 2.2e-16\n\n\nAlternatively you can use the tidy() function from the broom package to pull the results in to a table. What the results tell us is that for each year increase in age the height increases by 3.5 cm and this is statistically significant.\n\ntidy(lm_results)\n\n# A tibble: 2 × 5\n term estimate std.error statistic p.value\n <chr> <dbl> <dbl> <dbl> <dbl>\n1 (Intercept) 69.9 0.598 117. 0\n2 age 3.44 0.0293 117. 0\n\n\nYou can then also use this regression to add it to a ggplot, to do this we first pull the points for the observed data and the fitted line in to one data frame using the augment() function from broom.\n\n## pull the regression points and observed data in to one dataset\npoints <- augment(lm_results)\n\n## plot the data using age as the x-axis \nggplot(points, aes(x = age)) + \n ## add points for height \n geom_point(aes(y = ht_cm)) + \n ## add your regression line \n geom_line(aes(y = .fitted), colour = \"red\")\n\n\n\n\n\n\n\n\nIt is also possible to add a simple linear regression straight straight in ggplot using the geom_smooth() function.\n\n## add your data to a plot \n ggplot(linelist, aes(x = age, y = ht_cm)) + \n ## show points\n geom_point() + \n ## add a linear regression \n geom_smooth(method = \"lm\", se = FALSE)\n\n`geom_smooth()` using formula = 'y ~ x'\n\n\n\n\n\n\n\n\n\nSee the Resource section at the end of this chapter for more detailed tutorials.\n\n\nLogistic regression\nThe function glm() from the stats package (part of base R) is used to fit Generalized Linear Models (GLM).\nglm() can be used for univariate and multivariable logistic regression (e.g. to get Odds Ratios). Here are the core parts:\n\n# arguments for glm()\nglm(formula, family, data, weights, subset, ...)\n\n\nformula = The model is provided to glm() as an equation, with the outcome on the left and explanatory variables on the right of a tilde ~.\n\nfamily = This determines the type of model to run. For logistic regression, use family = \"binomial\", for poisson use family = \"poisson\". Other examples are in the table below.\n\ndata = Specify your data frame\n\nIf necessary, you can also specify the link function via the syntax family = familytype(link = \"linkfunction\")). You can read more in the documentation about other families and optional arguments such as weights = and subset = (?glm).\n\n\n\nFamily\nDefault link function\n\n\n\n\n\"binomial\"\n(link = \"logit\")\n\n\n\"gaussian\"\n(link = \"identity\")\n\n\n\"Gamma\"\n(link = \"inverse\")\n\n\n\"inverse.gaussian\"\n(link = \"1/mu^2\")\n\n\n\"poisson\"\n(link = \"log\")\n\n\n\"quasi\"\n(link = \"identity\", variance = \"constant\")\n\n\n\"quasibinomial\"\n(link = \"logit\")\n\n\n\"quasipoisson\"\n(link = \"log\")\n\n\n\nWhen running glm() it is most common to save the results as a named R object. Then you can print the results to your console using summary() as shown below, or perform other operations on the results (e.g. exponentiate).\nIf you need to run a negative binomial regression you can use the MASS package; the glm.nb() uses the same syntax as glm(). For a walk-through of different regressions, see the UCLA stats page.\n\n\nUnivariate glm()\nIn this example we are assessing the association between different age categories and the outcome of death (coded as 1 in the Preparation section). Below is a univariate model of outcome by age_cat. We save the model output as model and then print it with summary() to the console. Note the estimates provided are the log odds and that the baseline level is the first factor level of age_cat (“0-4”).\n\nmodel <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist)\nsummary(model)\n\n\nCall:\nglm(formula = outcome ~ age_cat, family = \"binomial\", data = linelist)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 0.233738 0.072805 3.210 0.00133 **\nage_cat5-9 -0.062898 0.101733 -0.618 0.53640 \nage_cat10-14 0.138204 0.107186 1.289 0.19726 \nage_cat15-19 -0.005565 0.113343 -0.049 0.96084 \nage_cat20-29 0.027511 0.102133 0.269 0.78765 \nage_cat30-49 0.063764 0.113771 0.560 0.57517 \nage_cat50-69 -0.387889 0.259240 -1.496 0.13459 \nage_cat70+ -0.639203 0.915770 -0.698 0.48518 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5705.1 on 4159 degrees of freedom\nAIC: 5721.1\n\nNumber of Fisher Scoring iterations: 4\n\n\nTo alter the baseline level of a given variable, ensure the column is class Factor and move the desired level to the first position with fct_relevel() (see page on Factors). For example, below we take column age_cat and set “20-29” as the baseline before piping the modified data frame into glm().\n\nlinelist %>% \n mutate(age_cat = fct_relevel(age_cat, \"20-29\", after = 0)) %>% \n glm(formula = outcome ~ age_cat, family = \"binomial\") %>% \n summary()\n\n\nCall:\nglm(formula = outcome ~ age_cat, family = \"binomial\", data = .)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 0.26125 0.07163 3.647 0.000265 ***\nage_cat0-4 -0.02751 0.10213 -0.269 0.787652 \nage_cat5-9 -0.09041 0.10090 -0.896 0.370220 \nage_cat10-14 0.11069 0.10639 1.040 0.298133 \nage_cat15-19 -0.03308 0.11259 -0.294 0.768934 \nage_cat30-49 0.03625 0.11302 0.321 0.748390 \nage_cat50-69 -0.41540 0.25891 -1.604 0.108625 \nage_cat70+ -0.66671 0.91568 -0.728 0.466546 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5705.1 on 4159 degrees of freedom\nAIC: 5721.1\n\nNumber of Fisher Scoring iterations: 4\n\n\n\n\nPrinting results\nFor most uses, several modifications must be made to the above outputs. The function tidy() from the package broom is convenient for making the model results presentable.\nHere we demonstrate how to combine model outputs with a table of counts.\n\nGet the exponentiated log odds ratio estimates and confidence intervals by passing the model to tidy() and setting exponentiate = TRUE and conf.int = TRUE.\n\n\nmodel <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist) %>% \n tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs\n mutate(across(where(is.numeric), round, digits = 2)) # round all numeric columns\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `across(where(is.numeric), round, digits = 2)`.\nCaused by warning:\n! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.\nSupply arguments directly to `.fns` through an anonymous function instead.\n\n # Previously\n across(a:b, mean, na.rm = TRUE)\n\n # Now\n across(a:b, \\(x) mean(x, na.rm = TRUE))\n\n\nBelow is the outputted tibble model:\n\n\n\n\n\n\n\nCombine these model results with a table of counts. Below, we create the a counts cross-table with the tabyl() function from janitor, as covered in the Descriptive tables page.\n\n\ncounts_table <- linelist %>% \n janitor::tabyl(age_cat, outcome)\n\n\n\n\n\n\n\n\n\n\n\nHere is what this counts_table data frame looks like:\n\n\n\n\n\n\nNow we can bind the counts_table and the model results together horizontally with bind_cols() (dplyr). Remember that with bind_cols() the rows in the two data frames must be aligned perfectly. In this code, because we are binding within a pipe chain, we use . to represent the piped object counts_table as we bind it to model. To finish the process, we use select() to pick the desired columns and their order, and finally apply the base R round() function across all numeric columns to specify 2 decimal places.\n\ncombined <- counts_table %>% # begin with table of counts\n bind_cols(., model) %>% # combine with the outputs of the regression \n select(term, 2:3, estimate, # select and re-order cols\n conf.low, conf.high, p.value) %>% \n mutate(across(where(is.numeric), round, digits = 2)) ## round to 2 decimal places\n\nHere is what the combined data frame looks like, printed nicely as an image with a function from flextable. The Tables for presentation explains how to customize such tables with flextable, or or you can use numerous other packages such as knitr or GT.\n\ncombined <- combined %>% \n flextable::qflextable()\n\n\n\nLooping multiple univariate models\nBelow we present a method using glm() and tidy() for a more simple approach, see the section on gtsummary.\nTo run the models on several exposure variables to produce univariate odds ratios (i.e. not controlling for each other), you can use the approach below. It uses str_c() from stringr to create univariate formulas (see Characters and strings), runs the glm() regression on each formula, passes each glm() output to tidy() and finally collapses all the model outputs together with bind_rows() from tidyr. This approach uses map() from the package purrr to iterate - see the page on Iteration, loops, and lists for more information on this tool.\n\nCreate a vector of column names of the explanatory variables. We already have this as explanatory_vars from the Preparation section of this page.\nUse str_c() to create multiple string formulas, with outcome on the left, and a column name from explanatory_vars on the right. The period . substitutes for the column name in explanatory_vars.\n\n\nexplanatory_vars %>% str_c(\"outcome ~ \", .)\n\n[1] \"outcome ~ gender\" \"outcome ~ fever\" \"outcome ~ chills\" \n[4] \"outcome ~ cough\" \"outcome ~ aches\" \"outcome ~ vomit\" \n[7] \"outcome ~ age_cat\"\n\n\n\nPass these string formulas to map() and set ~glm() as the function to apply to each input. Within glm(), set the regression formula as as.formula(.x) where .x will be replaced by the string formula defined in the step above. map() will loop over each of the string formulas, running regressions for each one.\nThe outputs of this first map() are passed to a second map() command, which applies tidy() to the regression outputs.\nFinally the output of the second map() (a list of tidied data frames) is condensed with bind_rows(), resulting in one data frame with all the univariate results.\n\n\nmodels <- explanatory_vars %>% # begin with variables of interest\n str_c(\"outcome ~ \", .) %>% # combine each variable into formula (\"outcome ~ variable of interest\")\n \n # iterate through each univariate formula\n map( \n .f = ~glm( # pass the formulas one-by-one to glm()\n formula = as.formula(.x), # within glm(), the string formula is .x\n family = \"binomial\", # specify type of glm (logistic)\n data = linelist)) %>% # dataset\n \n # tidy up each of the glm regression outputs from above\n map(\n .f = ~tidy(\n .x, \n exponentiate = TRUE, # exponentiate \n conf.int = TRUE)) %>% # return confidence intervals\n \n # collapse the list of regression outputs in to one data frame\n bind_rows() %>% \n \n # round all numeric columns\n mutate(across(where(is.numeric), round, digits = 2))\n\nThis time, the end object models is longer because it now represents the combined results of several univariate regressions. Click through to see all the rows of model.\n\n\n\n\n\n\nAs before, we can create a counts table from the linelist for each explanatory variable, bind it to models, and make a nice table. We begin with the variables, and iterate through them with map(). We iterate through a user-defined function which involves creating a counts table with dplyr functions. Then the results are combined and bound with the models model results.\n\n## for each explanatory variable\nuniv_tab_base <- explanatory_vars %>% \n map(.f = \n ~{linelist %>% ## begin with linelist\n group_by(outcome) %>% ## group data set by outcome\n count(.data[[.x]]) %>% ## produce counts for variable of interest\n pivot_wider( ## spread to wide format (as in cross-tabulation)\n names_from = outcome,\n values_from = n) %>% \n drop_na(.data[[.x]]) %>% ## drop rows with missings\n rename(\"variable\" = .x) %>% ## change variable of interest column to \"variable\"\n mutate(variable = as.character(variable))} ## convert to character, else non-dichotomous (categorical) variables come out as factor and cant be merged\n ) %>% \n \n ## collapse the list of count outputs in to one data frame\n bind_rows() %>% \n \n ## merge with the outputs of the regression \n bind_cols(., models) %>% \n \n ## only keep columns interested in \n select(term, 2:3, estimate, conf.low, conf.high, p.value) %>% \n \n ## round decimal places\n mutate(across(where(is.numeric), round, digits = 2))\n\nBelow is what the data frame looks like. See the page on Tables for presentation for ideas on how to further convert this table to pretty HTML output (e.g. with flextable).\n\n\n\n\n\n\n\n\n\n\ngtsummary package\nBelow we present the use of tbl_uvregression() from the gtsummary package. Just like in the page on Descriptive tables, gtsummary functions do a good job of running statistics and producing professional-looking outputs. This function produces a table of univariate regression results.\nWe select only the necessary columns from the linelist (explanatory variables and the outcome variable) and pipe them into tbl_uvregression(). We are going to run univariate regression on each of the columns we defined as explanatory_vars in the data Preparation section (gender, fever, chills, cough, aches, vomit, and age_cat).\nWithin the function itself, we provide the method = as glm (no quotes), the y = outcome column (outcome), specify to method.args = that we want to run logistic regression via family = binomial, and we tell it to exponentiate the results.\nThe output is HTML and contains the counts\n\nuniv_tab <- linelist %>% \n dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest\n\n tbl_uvregression( ## produce univariate table\n method = glm, ## define regression want to run (generalised linear model)\n y = outcome, ## define outcome variable\n method.args = list(family = binomial), ## define what type of glm want to run (logistic)\n exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds)\n )\n\n## view univariate results table \nuniv_tab\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN\nOR1\n95% CI1\np-value\n\n\n\n\ngender\n4,167\n1.00\n0.88, 1.13\n>0.9\n\n\nfever\n4,167\n1.00\n0.85, 1.17\n>0.9\n\n\nchills\n4,167\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n4,167\n1.15\n0.97, 1.37\n0.11\n\n\naches\n4,167\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n4,167\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n4,167\n\n\n\n\n\n\n\n\n    0-4\n\n\n—\n—\n\n\n\n\n    5-9\n\n\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n\n\n1.15\n0.93, 1.42\n0.2\n\n\n    15-19\n\n\n0.99\n0.80, 1.24\n>0.9\n\n\n    20-29\n\n\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n\n\n1.07\n0.85, 1.33\n0.6\n\n\n    50-69\n\n\n0.68\n0.41, 1.13\n0.13\n\n\n    70+\n\n\n0.53\n0.07, 3.20\n0.5\n\n\n\n1 OR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\n\nThere are many modifications you can make to this table output, such as adjusting the text labels, bolding rows by their p-value, etc. See tutorials here and elsewhere online.", + "section": "19.1 Univariate", + "text": "19.1 Univariate\nJust like in the page on Descriptive tables, your use case will determine which R package you use. We present two options for doing univariate analysis:\n\nUse functions available in base R to quickly print results to the console. Use the broom package to tidy up the outputs.\n\nUse the gtsummary package to model and get publication-ready outputs.\n\n\n\nbase R\n\nLinear regression\nThe base R function lm() perform linear regression, assessing the relationship between numeric response and explanatory variables that are assumed to have a linear relationship.\nProvide the equation as a formula, with the response and explanatory column names separated by a tilde ~. Also, specify the dataset to data =. Define the model results as an R object, to use later.\n\nlm_results <- lm(ht_cm ~ age, data = linelist)\n\nYou can then run summary() on the model results to see the coefficients (Estimates), P-value, residuals, and other measures.\n\nsummary(lm_results)\n\n\nCall:\nlm(formula = ht_cm ~ age, data = linelist)\n\nResiduals:\n Min 1Q Median 3Q Max \n-128.579 -15.854 1.177 15.887 175.483 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 69.9051 0.5979 116.9 <2e-16 ***\nage 3.4354 0.0293 117.2 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 23.75 on 4165 degrees of freedom\nMultiple R-squared: 0.7675, Adjusted R-squared: 0.7674 \nF-statistic: 1.375e+04 on 1 and 4165 DF, p-value: < 2.2e-16\n\n\nAlternatively you can use the tidy() function from the broom package to pull the results in to a table. What the results tell us is that for each year increase in age the height increases by 3.5 cm and this is statistically significant.\n\ntidy(lm_results)\n\n# A tibble: 2 × 5\n term estimate std.error statistic p.value\n <chr> <dbl> <dbl> <dbl> <dbl>\n1 (Intercept) 69.9 0.598 117. 0\n2 age 3.44 0.0293 117. 0\n\n\nYou can then also use this regression to add it to a ggplot, to do this we first pull the points for the observed data and the fitted line in to one data frame using the augment() function from broom.\n\n## pull the regression points and observed data in to one dataset\npoints <- augment(lm_results)\n\n## plot the data using age as the x-axis \nggplot(points, aes(x = age)) + \n ## add points for height \n geom_point(aes(y = ht_cm)) + \n ## add your regression line \n geom_line(aes(y = .fitted), colour = \"red\")\n\n\n\n\n\n\n\n\nIt is also possible to add a simple linear regression straight straight in ggplot using the geom_smooth() function.\n\n## add your data to a plot \n ggplot(linelist, aes(x = age, y = ht_cm)) + \n ## show points\n geom_point() + \n ## add a linear regression \n geom_smooth(method = \"lm\", se = FALSE)\n\n`geom_smooth()` using formula = 'y ~ x'\n\n\n\n\n\n\n\n\n\nSee the Resource section at the end of this chapter for more detailed tutorials.\n\n\nLogistic regression\nThe function glm() from the stats package (part of base R) is used to fit Generalized Linear Models (GLM).\nglm() can be used for univariate and multivariable logistic regression (e.g. to get Odds Ratios). Here are the core parts:\n\n# arguments for glm()\nglm(formula, family, data, weights, subset, ...)\n\n\nformula = The model is provided to glm() as an equation, with the outcome on the left and explanatory variables on the right of a tilde ~.\n\nfamily = This determines the type of model to run. For logistic regression, use family = \"binomial\", for poisson use family = \"poisson\". Other examples are in the table below.\n\ndata = Specify your data frame.\n\nIf necessary, you can also specify the link function via the syntax family = familytype(link = \"linkfunction\")). You can read more in the documentation about other families and optional arguments such as weights = and subset = (?glm).\n\n\n\nFamily\nDefault link function\n\n\n\n\n\"binomial\"\n(link = \"logit\")\n\n\n\"gaussian\"\n(link = \"identity\")\n\n\n\"Gamma\"\n(link = \"inverse\")\n\n\n\"inverse.gaussian\"\n(link = \"1/mu^2\")\n\n\n\"poisson\"\n(link = \"log\")\n\n\n\"quasi\"\n(link = \"identity\", variance = \"constant\")\n\n\n\"quasibinomial\"\n(link = \"logit\")\n\n\n\"quasipoisson\"\n(link = \"log\")\n\n\n\nWhen running glm() it is most common to save the results as a named R object. Then you can print the results to your console using summary() as shown below, or perform other operations on the results (e.g. exponentiate).\nIf you need to run a negative binomial regression you can use the MASS package; the glm.nb() uses the same syntax as glm(). For a walk-through of different regressions, see the UCLA stats page.\n\n\nUnivariate glm()\nIn this example we are assessing the association between different age categories and the outcome of death (coded as 1 in the Preparation section). Below is a univariate model of outcome by age_cat. We save the model output as model and then print it with summary() to the console. Note the estimates provided are the log odds and that the baseline level is the first factor level of age_cat (“0-4”).\n\nmodel <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist)\nsummary(model)\n\n\nCall:\nglm(formula = outcome ~ age_cat, family = \"binomial\", data = linelist)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 0.233738 0.072805 3.210 0.00133 **\nage_cat5-9 -0.062898 0.101733 -0.618 0.53640 \nage_cat10-14 0.138204 0.107186 1.289 0.19726 \nage_cat15-19 -0.005565 0.113343 -0.049 0.96084 \nage_cat20-29 0.027511 0.102133 0.269 0.78765 \nage_cat30-49 0.063764 0.113771 0.560 0.57517 \nage_cat50-69 -0.387889 0.259240 -1.496 0.13459 \nage_cat70+ -0.639203 0.915770 -0.698 0.48518 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5705.1 on 4159 degrees of freedom\nAIC: 5721.1\n\nNumber of Fisher Scoring iterations: 4\n\n\nTo alter the baseline level of a given variable, ensure the column is class Factor and move the desired level to the first position with fct_relevel() (see page on Factors). For example, below we take column age_cat and set “20-29” as the baseline before piping the modified data frame into glm().\n\nlinelist %>% \n mutate(age_cat = fct_relevel(age_cat, \"20-29\", after = 0)) %>% \n glm(formula = outcome ~ age_cat, family = \"binomial\") %>% \n summary()\n\n\nCall:\nglm(formula = outcome ~ age_cat, family = \"binomial\", data = .)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 0.26125 0.07163 3.647 0.000265 ***\nage_cat0-4 -0.02751 0.10213 -0.269 0.787652 \nage_cat5-9 -0.09041 0.10090 -0.896 0.370220 \nage_cat10-14 0.11069 0.10639 1.040 0.298133 \nage_cat15-19 -0.03308 0.11259 -0.294 0.768934 \nage_cat30-49 0.03625 0.11302 0.321 0.748390 \nage_cat50-69 -0.41540 0.25891 -1.604 0.108625 \nage_cat70+ -0.66671 0.91568 -0.728 0.466546 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5705.1 on 4159 degrees of freedom\nAIC: 5721.1\n\nNumber of Fisher Scoring iterations: 4\n\n\n\n\nPrinting results\nFor most uses, several modifications must be made to the above outputs. The function tidy() from the package broom is convenient for making the model results presentable.\nHere we demonstrate how to combine model outputs with a table of counts.\n\nGet the exponentiated log odds ratio estimates and confidence intervals by passing the model to tidy() and setting exponentiate = TRUE and conf.int = TRUE.\n\n\nmodel <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist) %>% \n tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs\n mutate(across(where(is.numeric), round, digits = 2)) # round all numeric columns\n\nBelow is the outputted tibble model:\n\n\n\n\n\n\n\nCombine these model results with a table of counts. Below, we create the a counts cross-table with the tabyl() function from janitor, as covered in the Descriptive tables page.\n\n\ncounts_table <- linelist %>% \n janitor::tabyl(age_cat, outcome)\n\n\n\n\n\n\n\n\n\n\n\nHere is what this counts_table data frame looks like:\n\n\n\n\n\n\nNow we can bind the counts_table and the model results together horizontally with bind_cols() from dplyr. Remember that with bind_cols() the rows in the two data frames must be aligned perfectly. In this code, because we are binding within a pipe chain, we use . to represent the piped object counts_table as we bind it to model. To finish the process, we use select() to pick the desired columns and their order, and finally apply the base R round() function across all numeric columns to specify 2 decimal places.\n\ncombined <- counts_table %>% # begin with table of counts\n bind_cols(., model) %>% # combine with the outputs of the regression \n select(term, 2:3, estimate, # select and re-order cols\n conf.low, conf.high, p.value) %>% \n mutate(across(where(is.numeric), round, digits = 2)) ## round to 2 decimal places\n\nHere is what the combined data frame looks like, printed nicely as an image with a function from flextable. The Tables for presentation explains how to customize such tables with flextable, or or you can use numerous other packages such as knitr or GT.\n\ncombined <- combined %>% \n flextable::qflextable()\n\n\n\nLooping multiple univariate models\nBelow we present a method using glm() and tidy() for a more simple approach, see the section on gtsummary.\nTo run the models on several exposure variables to produce univariate odds ratios (i.e. not controlling for each other), you can use the approach below. It uses str_c() from stringr to create univariate formulas (see Characters and strings), runs the glm() regression on each formula, passes each glm() output to tidy() and finally collapses all the model outputs together with bind_rows() from tidyr. This approach uses map() from the package purrr to iterate - see the page on Iteration, loops, and lists for more information on this tool.\n\nCreate a vector of column names of the explanatory variables. We already have this as explanatory_vars from the Preparation section of this page.\nUse str_c() to create multiple string formulas, with outcome on the left, and a column name from explanatory_vars on the right. The period . substitutes for the column name in explanatory_vars.\n\n\nexplanatory_vars %>% str_c(\"outcome ~ \", .)\n\n[1] \"outcome ~ gender\" \"outcome ~ fever\" \"outcome ~ chills\" \n[4] \"outcome ~ cough\" \"outcome ~ aches\" \"outcome ~ vomit\" \n[7] \"outcome ~ age_cat\"\n\n\n\nPass these string formulas to map() and set ~glm() as the function to apply to each input. Within glm(), set the regression formula as as.formula(.x) where .x will be replaced by the string formula defined in the step above. map() will loop over each of the string formulas, running regressions for each one.\nThe outputs of this first map() are passed to a second map() command, which applies tidy() to the regression outputs.\nFinally the output of the second map() (a list of tidied data frames) is condensed with bind_rows(), resulting in one data frame with all the univariate results.\n\n\nmodels <- explanatory_vars %>% # begin with variables of interest\n str_c(\"outcome ~ \", .) %>% # combine each variable into formula (\"outcome ~ variable of interest\")\n \n # iterate through each univariate formula\n map( \n .f = ~glm( # pass the formulas one-by-one to glm()\n formula = as.formula(.x), # within glm(), the string formula is .x\n family = \"binomial\", # specify type of glm (logistic)\n data = linelist)) %>% # dataset\n \n # tidy up each of the glm regression outputs from above\n map(\n .f = ~tidy(\n .x, \n exponentiate = TRUE, # exponentiate \n conf.int = TRUE)) %>% # return confidence intervals\n \n # collapse the list of regression outputs in to one data frame\n bind_rows() %>% \n \n # round all numeric columns\n mutate(across(where(is.numeric), round, digits = 2))\n\nThis time, the end object models is longer because it now represents the combined results of several univariate regressions. Click through to see all the rows of model.\n\n\n\n\n\n\nAs before, we can create a counts table from the linelist for each explanatory variable, bind it to models, and make a nice table. We begin with the variables, and iterate through them with map(). We iterate through a user-defined function which involves creating a counts table with dplyr functions. Then the results are combined and bound with the models model results.\n\n## for each explanatory variable\nuniv_tab_base <- explanatory_vars %>% \n map(.f = \n ~{linelist %>% ## begin with linelist\n group_by(outcome) %>% ## group data set by outcome\n count(.data[[.x]]) %>% ## produce counts for variable of interest\n pivot_wider( ## spread to wide format (as in cross-tabulation)\n names_from = outcome,\n values_from = n) %>% \n drop_na(.data[[.x]]) %>% ## drop rows with missings\n rename(\"variable\" = .x) %>% ## change variable of interest column to \"variable\"\n mutate(variable = as.character(variable))} ## convert to character, else non-dichotomous (categorical) variables come out as factor and cant be merged\n ) %>% \n \n ## collapse the list of count outputs in to one data frame\n bind_rows() %>% \n \n ## merge with the outputs of the regression \n bind_cols(., models) %>% \n \n ## only keep columns interested in \n select(term, 2:3, estimate, conf.low, conf.high, p.value) %>% \n \n ## round decimal places\n mutate(across(where(is.numeric), round, digits = 2))\n\nBelow is what the data frame looks like. See the page on Tables for presentation for ideas on how to further convert this table to pretty HTML output (e.g. with flextable).\n\n\n\n\n\n\n\n\n\n\ngtsummary package\nBelow we present the use of tbl_uvregression() from the gtsummary package. Just like in the page on Descriptive tables, gtsummary functions do a good job of running statistics and producing professional-looking outputs. This function produces a table of univariate regression results.\nWe select only the necessary columns from the linelist (explanatory variables and the outcome variable) and pipe them into tbl_uvregression(). We are going to run univariate regression on each of the columns we defined as explanatory_vars in the data Preperation section (gender, fever, chills, cough, aches, vomit, and age_cat).\nWithin the function itself, we provide the method = as glm (no quotes), the y = outcome column (outcome), specify to method.args = that we want to run logistic regression via family = binomial, and we tell it to exponentiate the results.\nThe output is HTML and contains the counts:\n\nuniv_tab <- linelist %>% \n dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest\n\n tbl_uvregression( ## produce univariate table\n method = glm, ## define regression want to run (generalised linear model)\n y = outcome, ## define outcome variable\n method.args = list(family = binomial), ## define what type of glm want to run (logistic)\n exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds)\n )\n\n## view univariate results table \nuniv_tab\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN\nOR\n1\n95% CI\n1\np-value\n\n\n\n\ngender\n4,167\n1.00\n0.88, 1.13\n>0.9\n\n\nfever\n4,167\n1.00\n0.85, 1.17\n>0.9\n\n\nchills\n4,167\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n4,167\n1.15\n0.97, 1.37\n0.11\n\n\naches\n4,167\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n4,167\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n4,167\n\n\n\n\n\n\n\n\n    0-4\n\n\n—\n—\n\n\n\n\n    5-9\n\n\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n\n\n1.15\n0.93, 1.42\n0.2\n\n\n    15-19\n\n\n0.99\n0.80, 1.24\n>0.9\n\n\n    20-29\n\n\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n\n\n1.07\n0.85, 1.33\n0.6\n\n\n    50-69\n\n\n0.68\n0.41, 1.13\n0.13\n\n\n    70+\n\n\n0.53\n0.07, 3.20\n0.5\n\n\n\n1\nOR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\n\n\n19.1.1 Cross-tabulation\nThe gtsummary package also allows us to quickly and easily create tables of counts. This can be useful for quickly summarising the data, and putting it in context with the regression we have carried out.\n\n#Carry out our regression\nuniv_tab <- linelist %>% \n dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest\n\n tbl_uvregression( ## produce univariate table\n method = glm, ## define regression want to run (generalised linear model)\n y = outcome, ## define outcome variable\n method.args = list(family = binomial), ## define what type of glm want to run (logistic)\n exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds)\n )\n\n#Create our cross tabulation\ncross_tab <- linelist %>%\n dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest\n tbl_summary(by = outcome) ## create summary table\n\ntbl_merge(tbls = list(cross_tab,\n univ_tab),\n tab_spanner = c(\"Summary\", \"Univariate regression\"))\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\n\nSummary\n\n\nUnivariate regression\n\n\n\n0\nN = 1,825\n1\n1\nN = 2,342\n1\nN\nOR\n2\n95% CI\n2\np-value\n\n\n\n\ngender\n916 (50%)\n1,174 (50%)\n4,167\n1.00\n0.88, 1.13\n>0.9\n\n\nfever\n1,485 (81%)\n1,906 (81%)\n4,167\n1.00\n0.85, 1.17\n>0.9\n\n\nchills\n353 (19%)\n465 (20%)\n4,167\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n1,553 (85%)\n2,033 (87%)\n4,167\n1.15\n0.97, 1.37\n0.11\n\n\naches\n189 (10%)\n228 (9.7%)\n4,167\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n894 (49%)\n1,198 (51%)\n4,167\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n\n\n\n\n4,167\n\n\n\n\n\n\n\n\n    0-4\n338 (19%)\n427 (18%)\n\n\n—\n—\n\n\n\n\n    5-9\n365 (20%)\n433 (18%)\n\n\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n273 (15%)\n396 (17%)\n\n\n1.15\n0.93, 1.42\n0.2\n\n\n    15-19\n238 (13%)\n299 (13%)\n\n\n0.99\n0.80, 1.24\n>0.9\n\n\n    20-29\n345 (19%)\n448 (19%)\n\n\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n228 (12%)\n307 (13%)\n\n\n1.07\n0.85, 1.33\n0.6\n\n\n    50-69\n35 (1.9%)\n30 (1.3%)\n\n\n0.68\n0.41, 1.13\n0.13\n\n\n    70+\n3 (0.2%)\n2 (<0.1%)\n\n\n0.53\n0.07, 3.20\n0.5\n\n\n\n1\nn (%)\n\n\n2\nOR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\nThere are many modifications you can make to this table output, such as adjusting the text labels, bolding rows by their p-value, etc. See tutorials here and elsewhere online.", "crumbs": [ "Analysis", "19  Univariate and multivariable regression" @@ -1714,8 +1714,8 @@ "objectID": "new_pages/regression.html#stratified", "href": "new_pages/regression.html#stratified", "title": "19  Univariate and multivariable regression", - "section": "19.3 Stratified", - "text": "19.3 Stratified\nStratified analysis is currently still being worked on for gtsummary, this page will be updated in due course.", + "section": "19.2 Stratified", + "text": "19.2 Stratified\nHere we define stratified regression as the process of carrying out separate regression analyses on different “groups” of data.\nSometimes in your analysis, you will want to investigate whether or not there are different relationships between an outcome and variables, by different strata. This could be something like, a difference in gender, age group, or source of infection.\nTo do this, you will want to split your dataset into the strata of interest. For example, creating two separate datasets of gender == \"f\" and gender == \"m\", would be done by:\n\nf_linelist <- linelist %>%\n filter(gender == 0) %>% ## subset to only where the gender == \"f\"\n dplyr::select(explanatory_vars, outcome) ## select variables of interest\n \nm_linelist <- linelist %>%\n filter(gender == 1) %>% ## subset to only where the gender == \"f\"\n dplyr::select(explanatory_vars, outcome) ## select variables of interest\n\nOnce this has been done, you can carry out your regression in either base R or gtsummary.\n\n19.2.1 base R\nTo carry this out in base R, you run two different regressions, one for where gender == \"f\" and gender == \"m\".\n\n#Run model for f\nf_model <- glm(outcome ~ vomit, family = \"binomial\", data = f_linelist) %>% \n tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs\n mutate(across(where(is.numeric), round, digits = 2)) %>% # round all numeric columns\n mutate(gender = \"f\") # create a column which identifies these results as using the f dataset\n \n#Run model for m\nm_model <- glm(outcome ~ vomit, family = \"binomial\", data = m_linelist) %>% \n tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs\n mutate(across(where(is.numeric), round, digits = 2)) %>% # round all numeric columns\n mutate(gender = \"m\") # create a column which identifies these results as using the m dataset\n\n#Combine the results\nrbind(f_model,\n m_model)\n\n# A tibble: 4 × 8\n term estimate std.error statistic p.value conf.low conf.high gender\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> \n1 (Intercept) 1.25 0.06 3.56 0 1.11 1.42 f \n2 vomit 1.05 0.09 0.58 0.56 0.89 1.25 f \n3 (Intercept) 1.21 0.06 3.04 0 1.07 1.36 m \n4 vomit 1.13 0.09 1.38 0.17 0.95 1.34 m \n\n\n\n\n19.2.2 gtsummary\nThe same approach is repeated using gtsummary, however it is easier to produce publication ready tables with gtsummary and compare the two tables with the function tbl_merge().\n\n#Run model for f\nf_model_gt <- f_linelist %>% \n dplyr::select(vomit, outcome) %>% ## select variables of interest\n tbl_uvregression( ## produce univariate table\n method = glm, ## define regression want to run (generalised linear model)\n y = outcome, ## define outcome variable\n method.args = list(family = binomial), ## define what type of glm want to run (logistic)\n exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds)\n )\n\n#Run model for m\nm_model_gt <- m_linelist %>% \n dplyr::select(vomit, outcome) %>% ## select variables of interest\n tbl_uvregression( ## produce univariate table\n method = glm, ## define regression want to run (generalised linear model)\n y = outcome, ## define outcome variable\n method.args = list(family = binomial), ## define what type of glm want to run (logistic)\n exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds)\n )\n\n#Combine gtsummary tables\nf_and_m_table <- tbl_merge(\n tbls = list(f_model_gt,\n m_model_gt),\n tab_spanner = c(\"Female\",\n \"Male\")\n)\n\n#Print\nf_and_m_table\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\n\nFemale\n\n\nMale\n\n\n\nN\nOR\n1\n95% CI\n1\np-value\nN\nOR\n1\n95% CI\n1\np-value\n\n\n\n\nvomit\n2,077\n1.05\n0.89, 1.25\n0.6\n2,090\n1.13\n0.95, 1.34\n0.2\n\n\n\n1\nOR = Odds Ratio, CI = Confidence Interval", "crumbs": [ "Analysis", "19  Univariate and multivariable regression" @@ -1725,8 +1725,8 @@ "objectID": "new_pages/regression.html#multivariable", "href": "new_pages/regression.html#multivariable", "title": "19  Univariate and multivariable regression", - "section": "19.4 Multivariable", - "text": "19.4 Multivariable\nFor multivariable analysis, we again present two approaches:\n\nglm() and tidy()\n\ngtsummary package\n\nThe workflow is similar for each and only the last step of pulling together a final table is different.\n\nConduct multivariable\nHere we use glm() but add more variables to the right side of the equation, separated by plus symbols (+).\nTo run the model with all of our explanatory variables we would run:\n\nmv_reg <- glm(outcome ~ gender + fever + chills + cough + aches + vomit + age_cat, family = \"binomial\", data = linelist)\n\nsummary(mv_reg)\n\n\nCall:\nglm(formula = outcome ~ gender + fever + chills + cough + aches + \n vomit + age_cat, family = \"binomial\", data = linelist)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 0.069054 0.131726 0.524 0.600\ngender 0.002448 0.065133 0.038 0.970\nfever 0.004309 0.080522 0.054 0.957\nchills 0.034112 0.078924 0.432 0.666\ncough 0.138584 0.089909 1.541 0.123\naches -0.070705 0.104078 -0.679 0.497\nvomit 0.086098 0.062618 1.375 0.169\nage_cat5-9 -0.063562 0.101851 -0.624 0.533\nage_cat10-14 0.136372 0.107275 1.271 0.204\nage_cat15-19 -0.011074 0.113640 -0.097 0.922\nage_cat20-29 0.026552 0.102780 0.258 0.796\nage_cat30-49 0.059569 0.116402 0.512 0.609\nage_cat50-69 -0.388964 0.262384 -1.482 0.138\nage_cat70+ -0.647443 0.917375 -0.706 0.480\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5700.2 on 4153 degrees of freedom\nAIC: 5728.2\n\nNumber of Fisher Scoring iterations: 4\n\n\nIf you want to include two variables and an interaction between them you can separate them with an asterisk * instead of a +. Separate them with a colon : if you are only specifying the interaction. For example:\n\nglm(outcome ~ gender + age_cat * fever, family = \"binomial\", data = linelist)\n\nOptionally, you can use this code to leverage the pre-defined vector of column names and re-create the above command using str_c(). This might be useful if your explanatory variable names are changing, or you don’t want to type them all out again.\n\n## run a regression with all variables of interest \nmv_reg <- explanatory_vars %>% ## begin with vector of explanatory column names\n str_c(collapse = \"+\") %>% ## combine all names of the variables of interest separated by a plus\n str_c(\"outcome ~ \", .) %>% ## combine the names of variables of interest with outcome in formula style\n glm(family = \"binomial\", ## define type of glm as logistic,\n data = linelist) ## define your dataset\n\n\nBuilding the model\nYou can build your model step-by-step, saving various models that include certain explanatory variables. You can compare these models with likelihood-ratio tests using lrtest() from the package lmtest, as below:\nNOTE: Using base anova(model1, model2, test = \"Chisq) produces the same results \n\nmodel1 <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist)\nmodel2 <- glm(outcome ~ age_cat + gender, family = \"binomial\", data = linelist)\n\nlmtest::lrtest(model1, model2)\n\nLikelihood ratio test\n\nModel 1: outcome ~ age_cat\nModel 2: outcome ~ age_cat + gender\n #Df LogLik Df Chisq Pr(>Chisq)\n1 8 -2852.6 \n2 9 -2852.6 1 2e-04 0.9883\n\n\nAnother option is to take the model object and apply the step() function from the stats package. Specify which variable selection direction you want use when building the model.\n\n## choose a model using forward selection based on AIC\n## you can also do \"backward\" or \"both\" by adjusting the direction\nfinal_mv_reg <- mv_reg %>%\n step(direction = \"forward\", trace = FALSE)\n\nYou can also turn off scientific notation in your R session, for clarity:\n\noptions(scipen=999)\n\nAs described in the section on univariate analysis, pass the model output to tidy() to exponentiate the log odds and CIs. Finally we round all numeric columns to two decimal places. Scroll through to see all the rows.\n\nmv_tab_base <- final_mv_reg %>% \n broom::tidy(exponentiate = TRUE, conf.int = TRUE) %>% ## get a tidy dataframe of estimates \n mutate(across(where(is.numeric), round, digits = 2)) ## round \n\nHere is what the resulting data frame looks like:\n\n\n\n\n\n\n\n\n\n\nCombine univariate and multivariable\n\nCombine with gtsummary\nThe gtsummary package provides the tbl_regression() function, which will take the outputs from a regression (glm() in this case) and produce an nice summary table.\n\n## show results table of final regression \nmv_tab <- tbl_regression(final_mv_reg, exponentiate = TRUE)\n\nLet’s see the table:\n\nmv_tab\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nOR1\n95% CI1\np-value\n\n\n\n\ngender\n1.00\n0.88, 1.14\n>0.9\n\n\nfever\n1.00\n0.86, 1.18\n>0.9\n\n\nchills\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n1.15\n0.96, 1.37\n0.12\n\n\naches\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n\n\n\n\n\n\n\n\n    0-4\n—\n—\n\n\n\n\n    5-9\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n1.15\n0.93, 1.41\n0.2\n\n\n    15-19\n0.99\n0.79, 1.24\n>0.9\n\n\n    20-29\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n1.06\n0.85, 1.33\n0.6\n\n\n    50-69\n0.68\n0.40, 1.13\n0.14\n\n\n    70+\n0.52\n0.07, 3.19\n0.5\n\n\n\n1 OR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\n\nYou can also combine several different output tables produced by gtsummary with the tbl_merge() function. We now combine the multivariable results with the gtsummary univariate results that we created above:\n\n## combine with univariate results \ntbl_merge(\n tbls = list(univ_tab, mv_tab), # combine\n tab_spanner = c(\"**Univariate**\", \"**Multivariable**\")) # set header names\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nUnivariate\nMultivariable\n\n\nN\nOR1\n95% CI1\np-value\nOR1\n95% CI1\np-value\n\n\n\n\ngender\n4,167\n1.00\n0.88, 1.13\n>0.9\n1.00\n0.88, 1.14\n>0.9\n\n\nfever\n4,167\n1.00\n0.85, 1.17\n>0.9\n1.00\n0.86, 1.18\n>0.9\n\n\nchills\n4,167\n1.03\n0.89, 1.21\n0.7\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n4,167\n1.15\n0.97, 1.37\n0.11\n1.15\n0.96, 1.37\n0.12\n\n\naches\n4,167\n0.93\n0.76, 1.14\n0.5\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n4,167\n1.09\n0.96, 1.23\n0.2\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n4,167\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n    0-4\n\n\n—\n—\n\n\n—\n—\n\n\n\n\n    5-9\n\n\n0.94\n0.77, 1.15\n0.5\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n\n\n1.15\n0.93, 1.42\n0.2\n1.15\n0.93, 1.41\n0.2\n\n\n    15-19\n\n\n0.99\n0.80, 1.24\n>0.9\n0.99\n0.79, 1.24\n>0.9\n\n\n    20-29\n\n\n1.03\n0.84, 1.26\n0.8\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n\n\n1.07\n0.85, 1.33\n0.6\n1.06\n0.85, 1.33\n0.6\n\n\n    50-69\n\n\n0.68\n0.41, 1.13\n0.13\n0.68\n0.40, 1.13\n0.14\n\n\n    70+\n\n\n0.53\n0.07, 3.20\n0.5\n0.52\n0.07, 3.19\n0.5\n\n\n\n1 OR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\n\n\n\nCombine with dplyr\nAn alternative way of combining the glm()/tidy() univariate and multivariable outputs is with the dplyr join functions.\n\nJoin the univariate results from earlier (univ_tab_base, which contains counts) with the tidied multivariable results mv_tab_base\n\nUse select() to keep only the columns we want, specify their order, and re-name them\n\nUse round() with two decimal places on all the column that are class Double\n\n\n## combine univariate and multivariable tables \nleft_join(univ_tab_base, mv_tab_base, by = \"term\") %>% \n ## choose columns and rename them\n select( # new name = old name\n \"characteristic\" = term, \n \"recovered\" = \"0\", \n \"dead\" = \"1\", \n \"univ_or\" = estimate.x, \n \"univ_ci_low\" = conf.low.x, \n \"univ_ci_high\" = conf.high.x,\n \"univ_pval\" = p.value.x, \n \"mv_or\" = estimate.y, \n \"mvv_ci_low\" = conf.low.y, \n \"mv_ci_high\" = conf.high.y,\n \"mv_pval\" = p.value.y \n ) %>% \n mutate(across(where(is.double), round, 2)) \n\n# A tibble: 20 × 11\n characteristic recovered dead univ_or univ_ci_low univ_ci_high univ_pval\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n 1 (Intercept) 909 1168 1.28 1.18 1.4 0 \n 2 gender 916 1174 1 0.88 1.13 0.97\n 3 (Intercept) 340 436 1.28 1.11 1.48 0 \n 4 fever 1485 1906 1 0.85 1.17 0.99\n 5 (Intercept) 1472 1877 1.28 1.19 1.37 0 \n 6 chills 353 465 1.03 0.89 1.21 0.68\n 7 (Intercept) 272 309 1.14 0.97 1.34 0.13\n 8 cough 1553 2033 1.15 0.97 1.37 0.11\n 9 (Intercept) 1636 2114 1.29 1.21 1.38 0 \n10 aches 189 228 0.93 0.76 1.14 0.51\n11 (Intercept) 931 1144 1.23 1.13 1.34 0 \n12 vomit 894 1198 1.09 0.96 1.23 0.17\n13 (Intercept) 338 427 1.26 1.1 1.46 0 \n14 age_cat5-9 365 433 0.94 0.77 1.15 0.54\n15 age_cat10-14 273 396 1.15 0.93 1.42 0.2 \n16 age_cat15-19 238 299 0.99 0.8 1.24 0.96\n17 age_cat20-29 345 448 1.03 0.84 1.26 0.79\n18 age_cat30-49 228 307 1.07 0.85 1.33 0.58\n19 age_cat50-69 35 30 0.68 0.41 1.13 0.13\n20 age_cat70+ 3 2 0.53 0.07 3.2 0.49\n# ℹ 4 more variables: mv_or <dbl>, mvv_ci_low <dbl>, mv_ci_high <dbl>,\n# mv_pval <dbl>", + "section": "19.3 Multivariable", + "text": "19.3 Multivariable\nFor multivariable analysis, we again present two approaches:\n\nglm() and tidy().\n\ngtsummary package.\n\nThe workflow is similar for each and only the last step of pulling together a final table is different.\n\nConduct multivariable\nHere we use glm() but add more variables to the right side of the equation, separated by plus symbols (+).\nTo run the model with all of our explanatory variables we would run:\n\nmv_reg <- glm(outcome ~ gender + fever + chills + cough + aches + vomit + age_cat, family = \"binomial\", data = linelist)\n\nsummary(mv_reg)\n\n\nCall:\nglm(formula = outcome ~ gender + fever + chills + cough + aches + \n vomit + age_cat, family = \"binomial\", data = linelist)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 0.069054 0.131726 0.524 0.600\ngender 0.002448 0.065133 0.038 0.970\nfever 0.004309 0.080522 0.054 0.957\nchills 0.034112 0.078924 0.432 0.666\ncough 0.138584 0.089909 1.541 0.123\naches -0.070705 0.104078 -0.679 0.497\nvomit 0.086098 0.062618 1.375 0.169\nage_cat5-9 -0.063562 0.101851 -0.624 0.533\nage_cat10-14 0.136372 0.107275 1.271 0.204\nage_cat15-19 -0.011074 0.113640 -0.097 0.922\nage_cat20-29 0.026552 0.102780 0.258 0.796\nage_cat30-49 0.059569 0.116402 0.512 0.609\nage_cat50-69 -0.388964 0.262384 -1.482 0.138\nage_cat70+ -0.647443 0.917375 -0.706 0.480\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5700.2 on 4153 degrees of freedom\nAIC: 5728.2\n\nNumber of Fisher Scoring iterations: 4\n\n\nIf you want to include two variables and an interaction between them you can separate them with an asterisk * instead of a +. Separate them with a colon : if you are only specifying the interaction. For example:\n\nglm(outcome ~ gender + age_cat * fever, family = \"binomial\", data = linelist)\n\nOptionally, you can use this code to leverage the pre-defined vector of column names and re-create the above command using str_c(). This might be useful if your explanatory variable names are changing, or you don’t want to type them all out again.\n\n## run a regression with all variables of interest \nmv_reg <- explanatory_vars %>% ## begin with vector of explanatory column names\n str_c(collapse = \"+\") %>% ## combine all names of the variables of interest separated by a plus\n str_c(\"outcome ~ \", .) %>% ## combine the names of variables of interest with outcome in formula style\n glm(family = \"binomial\", ## define type of glm as logistic,\n data = linelist) ## define your dataset\n\n\nBuilding the model\nYou can build your model step-by-step, saving various models that include certain explanatory variables. You can compare these models with likelihood-ratio tests using lrtest() from the package lmtest, as below:\nNOTE: Using base anova(model1, model2, test = \"Chisq) produces the same results \n\nmodel1 <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist)\nmodel2 <- glm(outcome ~ age_cat + gender, family = \"binomial\", data = linelist)\n\nlmtest::lrtest(model1, model2)\n\nLikelihood ratio test\n\nModel 1: outcome ~ age_cat\nModel 2: outcome ~ age_cat + gender\n #Df LogLik Df Chisq Pr(>Chisq)\n1 8 -2852.6 \n2 9 -2852.6 1 2e-04 0.9883\n\n\nAnother option is to take the model object and apply the step() function from the stats package. Specify which variable selection direction you want use when building the model.\n\n## choose a model using forward selection based on AIC\n## you can also do \"backward\" or \"both\" by adjusting the direction\nfinal_mv_reg <- mv_reg %>%\n step(direction = \"forward\", trace = FALSE)\n\nYou can also turn off scientific notation in your R session, for clarity:\n\noptions(scipen=999)\n\nAs described in the section on univariate analysis, pass the model output to tidy() to exponentiate the log odds and CIs. Finally we round all numeric columns to two decimal places. Scroll through to see all the rows.\n\nmv_tab_base <- final_mv_reg %>% \n broom::tidy(exponentiate = TRUE, conf.int = TRUE) %>% ## get a tidy dataframe of estimates \n mutate(across(where(is.numeric), round, digits = 2)) ## round \n\nHere is what the resulting data frame looks like:\n\n\n\n\n\n\n\n\n\n\nCombine univariate and multivariable\n\nCombine with gtsummary\nThe gtsummary package provides the tbl_regression() function, which will take the outputs from a regression (glm() in this case) and produce a nice summary table.\n\n## show results table of final regression \nmv_tab <- tbl_regression(final_mv_reg, exponentiate = TRUE)\n\nLet’s see the table:\n\nmv_tab\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nOR\n1\n95% CI\n1\np-value\n\n\n\n\ngender\n1.00\n0.88, 1.14\n>0.9\n\n\nfever\n1.00\n0.86, 1.18\n>0.9\n\n\nchills\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n1.15\n0.96, 1.37\n0.12\n\n\naches\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n\n\n\n\n\n\n\n\n    0-4\n—\n—\n\n\n\n\n    5-9\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n1.15\n0.93, 1.41\n0.2\n\n\n    15-19\n0.99\n0.79, 1.24\n>0.9\n\n\n    20-29\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n1.06\n0.85, 1.33\n0.6\n\n\n    50-69\n0.68\n0.40, 1.13\n0.14\n\n\n    70+\n0.52\n0.07, 3.19\n0.5\n\n\n\n1\nOR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\nYou can also combine several different output tables produced by gtsummary with the tbl_merge() function. We now combine the multivariable results with the gtsummary univariate results that we created above:\n\n## combine with univariate results \ntbl_merge(\n tbls = list(univ_tab, mv_tab), # combine\n tab_spanner = c(\"**Univariate**\", \"**Multivariable**\")) # set header names\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\n\nUnivariate\n\n\nMultivariable\n\n\n\nN\nOR\n1\n95% CI\n1\np-value\nOR\n1\n95% CI\n1\np-value\n\n\n\n\ngender\n4,167\n1.00\n0.88, 1.13\n>0.9\n1.00\n0.88, 1.14\n>0.9\n\n\nfever\n4,167\n1.00\n0.85, 1.17\n>0.9\n1.00\n0.86, 1.18\n>0.9\n\n\nchills\n4,167\n1.03\n0.89, 1.21\n0.7\n1.03\n0.89, 1.21\n0.7\n\n\ncough\n4,167\n1.15\n0.97, 1.37\n0.11\n1.15\n0.96, 1.37\n0.12\n\n\naches\n4,167\n0.93\n0.76, 1.14\n0.5\n0.93\n0.76, 1.14\n0.5\n\n\nvomit\n4,167\n1.09\n0.96, 1.23\n0.2\n1.09\n0.96, 1.23\n0.2\n\n\nage_cat\n4,167\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n    0-4\n\n\n—\n—\n\n\n—\n—\n\n\n\n\n    5-9\n\n\n0.94\n0.77, 1.15\n0.5\n0.94\n0.77, 1.15\n0.5\n\n\n    10-14\n\n\n1.15\n0.93, 1.42\n0.2\n1.15\n0.93, 1.41\n0.2\n\n\n    15-19\n\n\n0.99\n0.80, 1.24\n>0.9\n0.99\n0.79, 1.24\n>0.9\n\n\n    20-29\n\n\n1.03\n0.84, 1.26\n0.8\n1.03\n0.84, 1.26\n0.8\n\n\n    30-49\n\n\n1.07\n0.85, 1.33\n0.6\n1.06\n0.85, 1.33\n0.6\n\n\n    50-69\n\n\n0.68\n0.41, 1.13\n0.13\n0.68\n0.40, 1.13\n0.14\n\n\n    70+\n\n\n0.53\n0.07, 3.20\n0.5\n0.52\n0.07, 3.19\n0.5\n\n\n\n1\nOR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\n\n\nCombine with dplyr\nAn alternative way of combining the glm()/tidy() univariate and multivariable outputs is with the dplyr join functions.\n\nJoin the univariate results from earlier (univ_tab_base, which contains counts) with the tidied multivariable results mv_tab_base.\n\nUse select() to keep only the columns we want, specify their order, and re-name them.\n\nUse round() with two decimal places on all the column that are class Double.\n\n\n## combine univariate and multivariable tables \nleft_join(univ_tab_base, mv_tab_base, by = \"term\") %>% \n ## choose columns and rename them\n select( # new name = old name\n \"characteristic\" = term, \n \"recovered\" = \"0\", \n \"dead\" = \"1\", \n \"univ_or\" = estimate.x, \n \"univ_ci_low\" = conf.low.x, \n \"univ_ci_high\" = conf.high.x,\n \"univ_pval\" = p.value.x, \n \"mv_or\" = estimate.y, \n \"mvv_ci_low\" = conf.low.y, \n \"mv_ci_high\" = conf.high.y,\n \"mv_pval\" = p.value.y \n ) %>% \n mutate(across(where(is.double), round, 2)) \n\n# A tibble: 20 × 11\n characteristic recovered dead univ_or univ_ci_low univ_ci_high univ_pval\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n 1 (Intercept) 909 1168 1.28 1.18 1.4 0 \n 2 gender 916 1174 1 0.88 1.13 0.97\n 3 (Intercept) 340 436 1.28 1.11 1.48 0 \n 4 fever 1485 1906 1 0.85 1.17 0.99\n 5 (Intercept) 1472 1877 1.28 1.19 1.37 0 \n 6 chills 353 465 1.03 0.89 1.21 0.68\n 7 (Intercept) 272 309 1.14 0.97 1.34 0.13\n 8 cough 1553 2033 1.15 0.97 1.37 0.11\n 9 (Intercept) 1636 2114 1.29 1.21 1.38 0 \n10 aches 189 228 0.93 0.76 1.14 0.51\n11 (Intercept) 931 1144 1.23 1.13 1.34 0 \n12 vomit 894 1198 1.09 0.96 1.23 0.17\n13 (Intercept) 338 427 1.26 1.1 1.46 0 \n14 age_cat5-9 365 433 0.94 0.77 1.15 0.54\n15 age_cat10-14 273 396 1.15 0.93 1.42 0.2 \n16 age_cat15-19 238 299 0.99 0.8 1.24 0.96\n17 age_cat20-29 345 448 1.03 0.84 1.26 0.79\n18 age_cat30-49 228 307 1.07 0.85 1.33 0.58\n19 age_cat50-69 35 30 0.68 0.41 1.13 0.13\n20 age_cat70+ 3 2 0.53 0.07 3.2 0.49\n# ℹ 4 more variables: mv_or <dbl>, mvv_ci_low <dbl>, mv_ci_high <dbl>,\n# mv_pval <dbl>", "crumbs": [ "Analysis", "19  Univariate and multivariable regression" @@ -1736,8 +1736,8 @@ "objectID": "new_pages/regression.html#forest-plot", "href": "new_pages/regression.html#forest-plot", "title": "19  Univariate and multivariable regression", - "section": "19.5 Forest plot", - "text": "19.5 Forest plot\nThis section shows how to produce a plot with the outputs of your regression. There are two options, you can build a plot yourself using ggplot2 or use a meta-package called easystats (a package that includes many packages).\nSee the page on ggplot basics if you are unfamiliar with the ggplot2 plotting package.\n\n\nggplot2 package\nYou can build a forest plot with ggplot() by plotting elements of the multivariable regression results. Add the layers of the plots using these “geoms”:\n\nestimates with geom_point()\n\nconfidence intervals with geom_errorbar()\n\na vertical line at OR = 1 with geom_vline()\n\nBefore plotting, you may want to use fct_relevel() from the forcats package to set the order of the variables/levels on the y-axis. ggplot() may display them in alpha-numeric order which would not work well for these age category values (“30” would appear before “5”). See the page on Factors for more details.\n\n## remove the intercept term from your multivariable results\nmv_tab_base %>% \n \n #set order of levels to appear along y-axis\n mutate(term = fct_relevel(\n term,\n \"vomit\", \"gender\", \"fever\", \"cough\", \"chills\", \"aches\",\n \"age_cat5-9\", \"age_cat10-14\", \"age_cat15-19\", \"age_cat20-29\",\n \"age_cat30-49\", \"age_cat50-69\", \"age_cat70+\")) %>%\n \n # remove \"intercept\" row from plot\n filter(term != \"(Intercept)\") %>% \n \n ## plot with variable on the y axis and estimate (OR) on the x axis\n ggplot(aes(x = estimate, y = term)) +\n \n ## show the estimate as a point\n geom_point() + \n \n ## add in an error bar for the confidence intervals\n geom_errorbar(aes(xmin = conf.low, xmax = conf.high)) + \n \n ## show where OR = 1 is for reference as a dashed line\n geom_vline(xintercept = 1, linetype = \"dashed\")\n\n\n\n\n\n\n\n\n\n\n\neasystats packages\nAn alternative, if you do not want to the fine level of control that ggplot2 provides, is to use a combination of easystats packages.\nThe function model_parameters() from the parameters package does the equivalent of the broom package function tidy(). The see package then accepts those outputs and creates a default forest plot as a ggplot() object.\n\npacman::p_load(easystats)\n\n## remove the intercept term from your multivariable results\nfinal_mv_reg %>% \n model_parameters(exponentiate = TRUE) %>% \n plot()", + "section": "19.4 Forest plot", + "text": "19.4 Forest plot\nThis section shows how to produce a plot with the outputs of your regression. There are two options, you can build a plot yourself using ggplot2 or use a meta-package called easystats (a package that includes many packages).\nSee the page on ggplot basics if you are unfamiliar with the ggplot2 plotting package.\n\n\nggplot2 package\nYou can build a forest plot with ggplot() by plotting elements of the multivariable regression results. Add the layers of the plots using these “geoms”:\n\nestimates with geom_point().\n\nconfidence intervals with geom_errorbar().\n\na vertical line at OR = 1 with geom_vline().\n\nBefore plotting, you may want to use fct_relevel() from the forcats package to set the order of the variables/levels on the y-axis. ggplot() may display them in alpha-numeric order which would not work well for these age category values (“30” would appear before “5”). See the page on Factors for more details.\n\n## remove the intercept term from your multivariable results\nmv_tab_base %>% \n \n #set order of levels to appear along y-axis\n mutate(term = fct_relevel(\n term,\n \"vomit\", \"gender\", \"fever\", \"cough\", \"chills\", \"aches\",\n \"age_cat5-9\", \"age_cat10-14\", \"age_cat15-19\", \"age_cat20-29\",\n \"age_cat30-49\", \"age_cat50-69\", \"age_cat70+\")) %>%\n \n # remove \"intercept\" row from plot\n filter(term != \"(Intercept)\") %>% \n \n ## plot with variable on the y axis and estimate (OR) on the x axis\n ggplot(aes(x = estimate, y = term)) +\n \n ## show the estimate as a point\n geom_point() + \n \n ## add in an error bar for the confidence intervals\n geom_errorbar(aes(xmin = conf.low, xmax = conf.high)) + \n \n ## show where OR = 1 is for reference as a dashed line\n geom_vline(xintercept = 1, linetype = \"dashed\")\n\n\n\n\n\n\n\n\n\n\n\neasystats packages\nAn alternative, if you do not want to the fine level of control that ggplot2 provides, is to use a combination of easystats packages.\nThe function model_parameters() from the parameters package does the equivalent of the broom package function tidy(). The see package then accepts those outputs and creates a default forest plot as a ggplot() object.\n\npacman::p_load(easystats)\n\n## remove the intercept term from your multivariable results\nfinal_mv_reg %>% \n model_parameters(exponentiate = TRUE) %>% \n plot()", "crumbs": [ "Analysis", "19  Univariate and multivariable regression" @@ -1781,7 +1781,7 @@ "href": "new_pages/missing_data.html#missing-values-in-r", "title": "20  Missing data", "section": "20.2 Missing values in R", - "text": "20.2 Missing values in R\nBelow we explore ways that missingness is presented and assessed in R, along with some adjacent values and functions.\n\nNA\nIn R, missing values are represented by a reserved (special) value - NA. Note that this is typed without quotes. “NA” is different and is just a normal character value (also a Beatles lyric from the song Hey Jude).\nYour data may have other ways of representing missingness, such as “99”, or “Missing”, or “Unknown” - you may even have empty character value “” which looks “blank”, or a single space ” “. Be aware of these and consider whether to convert them to NA during import or during data cleaning with na_if().\nIn your data cleaning, you may also want to convert the other way - changing all NA to “Missing” or similar with replace_na() or with fct_explicit_na() for factors.\n\n\nVersions of NA\nMost of the time, NA represents a missing value and everything works fine. However, in some circumstances you may encounter the need for variations of NA specific to an object class (character, numeric, etc). This will be rare, but you should be aware.\nThe typical scenario for this is when creating a new column with the dplyr function case_when(). As described in the Cleaning data and core functions page, this function evaluates every row in the data frame, assess whether the rows meets specified logical criteria (right side of the code), and assigns the correct new value (left side of the code). Importantly: all values on the right side must be the same class.\n\nlinelist <- linelist %>% \n \n # Create new \"age_years\" column from \"age\" column\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age, # if age is given in years, assign original value\n age_unit == \"months\" ~ age/12, # if age is given in months, divide by 12\n is.na(age_unit) ~ age, # if age UNIT is missing, assume years\n TRUE ~ NA_real_)) # any other circumstance, assign missing\n\nIf you want NA on the right side, you may need to specify one of the special NA options listed below. If the other right side values are character, consider using “Missing” instead or otherwise use NA_character_. If they are all numeric, use NA_real_. If they are all dates or logical, you can use NA.\n\nNA - use for dates or logical TRUE/FALSE\nNA_character_ - use for characters\n\nNA_real_ - use for numeric\n\nAgain, it is not likely you will encounter these variations unless you are using case_when() to create a new column. See the R documentation on NA for more information.\n\n\nNULL\nNULL is another reserved value in R. It is the logical representation of a statement that is neither true nor false. It is returned by expressions or functions whose values are undefined. Generally do not assign NULL as a value, unless writing functions or perhaps writing a shiny app to return NULL in specific scenarios.\nNull-ness can be assessed using is.null() and conversion can made with as.null().\nSee this blog post on the difference between NULL and NA.\n\n\nNaN\nImpossible values are represented by the special value NaN. An example of this is when you force R to divide 0 by 0. You can assess this with is.nan(). You may also encounter complementary functions including is.infinite() and is.finite().\n\n\nInf\nInf represents an infinite value, such as when you divide a number by 0.\nAs an example of how this might impact your work: let’s say you have a vector/column z that contains these values: z <- c(1, 22, NA, Inf, NaN, 5)\nIf you want to use max() on the column to find the highest value, you can use the na.rm = TRUE to remove the NA from the calculation, but the Inf and NaN remain and Inf will be returned. To resolve this, you can use brackets [ ] and is.finite() to subset such that only finite values are used for the calculation: max(z[is.finite(z)]).\n\nz <- c(1, 22, NA, Inf, NaN, 5)\nmax(z) # returns NA\nmax(z, na.rm=T) # returns Inf\nmax(z[is.finite(z)]) # returns 22\n\n\n\nExamples\n\n\n\n\n\n\n\nR command\nOutcome\n\n\n\n\n5 / 0\nInf\n\n\n0 / 0\nNaN\n\n\n5 / NA\nNA\n\n\n5 / Inf |0NA - 5|NAInf / 5|Infclass(NA)| \"logical\"class(NaN)| \"numeric\"class(Inf)| \"numeric\"class(NULL)`\n“NULL”\n\n\n\n“NAs introduced by coercion” is a common warning message. This can happen if you attempt to make an illegal conversion like inserting a character value into a vector that is otherwise numeric.\n\nas.numeric(c(\"10\", \"20\", \"thirty\", \"40\"))\n\nWarning: NAs introduced by coercion\n\n\n[1] 10 20 NA 40\n\n\nNULL is ignored in a vector.\n\nmy_vector <- c(25, NA, 10, NULL) # define\nmy_vector # print\n\n[1] 25 NA 10\n\n\nVariance of one number results in NA.\n\nvar(22)\n\n[1] NA", + "text": "20.2 Missing values in R\nBelow we explore ways that missingness is presented and assessed in R, along with some adjacent values and functions.\n\nNA\nIn R, missing values are represented by a reserved (special) value - NA. Note that this is typed without quotes. “NA” is different and is just a normal character value (also a Beatles lyric from the song Hey Jude).\nYour data may have other ways of representing missingness, such as “99”, or “Missing”, or “Unknown” - you may even have empty character value “” which looks “blank”, or a single space ” “. Be aware of these and consider whether to convert them to NA during import or during data cleaning with na_if().\nIn your data cleaning, you may also want to convert the other way - changing all NA to “Missing” or similar with replace_na() or with fct_explicit_na() for factors.\n\n\nVersions of NA\nMost of the time, NA represents a missing value and everything works fine. However, in some circumstances you may encounter the need for variations of NA specific to an object class (character, numeric, etc). This will be rare, but you should be aware.\nThe typical scenario for this is when creating a new column with the dplyr function case_when(). As described in the Cleaning data and core functions page, this function evaluates every row in the data frame, assess whether the rows meets specified logical criteria (right side of the code), and assigns the correct new value (left side of the code). Importantly: all values on the right side must be the same class.\n\nlinelist <- linelist %>% \n \n # Create new \"age_years\" column from \"age\" column\n mutate(age_years = case_when(\n age_unit == \"years\" ~ age, # if age is given in years, assign original value\n age_unit == \"months\" ~ age/12, # if age is given in months, divide by 12\n is.na(age_unit) ~ age, # if age UNIT is missing, assume years\n TRUE ~ NA_real_)) # any other circumstance, assign missing\n\nIf you want NA on the right side, you may need to specify one of the special NA options listed below. If the other right side values are character, consider using “Missing” instead or otherwise use NA_character_. If they are all numeric, use NA_real_. If they are all dates or logical, you can use NA.\n\nNA - use for dates or logical TRUE/FALSE\nNA_character_ - use for characters\n\nNA_real_ - use for numeric\n\nAgain, it is not likely you will encounter these variations unless you are using case_when() to create a new column. See the R documentation on NA for more information.\n\n\nNULL\nNULL is another reserved value in R. It is the logical representation of a statement that is neither true nor false. It is returned by expressions or functions whose values are undefined. Generally do not assign NULL as a value, unless writing functions or perhaps writing a shiny app to return NULL in specific scenarios.\nNull-ness can be assessed using is.null() and conversion can made with as.null().\nSee this blog post on the difference between NULL and NA.\n\n\nNaN\nImpossible values are represented by the special value NaN. An example of this is when you force R to divide 0 by 0. You can assess this with is.nan(). You may also encounter complementary functions including is.infinite() and is.finite().\n\n\nInf\nInf represents an infinite value, such as when you divide a number by 0.\nAs an example of how this might impact your work: let’s say you have a vector/column z that contains these values: z <- c(1, 22, NA, Inf, NaN, 5).\nIf you want to use max() on the column to find the highest value, you can use the na.rm = TRUE to remove the NA from the calculation, but the Inf and NaN remain and Inf will be returned. To resolve this, you can use brackets [ ] and is.finite() to subset such that only finite values are used for the calculation: max(z[is.finite(z)]).\n\nz <- c(1, 22, NA, Inf, NaN, 5)\nmax(z) # returns NA\nmax(z, na.rm=T) # returns Inf\nmax(z[is.finite(z)]) # returns 22\n\n\n\nExamples\n\n\n\n\n\n\n\nR command\nOutcome\n\n\n\n\n5 / 0\nInf\n\n\n0 / 0\nNaN\n\n\n5 / NA\nNA\n\n\n5 / Inf |0NA - 5|NAInf / 5|Infclass(NA)| \"logical\"class(NaN)| \"numeric\"class(Inf)| \"numeric\"class(NULL)`\n“NULL”\n\n\n\n“NAs introduced by coercion” is a common warning message. This can happen if you attempt to make an illegal conversion like inserting a character value into a vector that is otherwise numeric.\n\nas.numeric(c(\"10\", \"20\", \"thirty\", \"40\"))\n\nWarning: NAs introduced by coercion\n\n\n[1] 10 20 NA 40\n\n\nNULL is ignored in a vector.\n\nmy_vector <- c(25, NA, 10, NULL) # define\nmy_vector # print\n\n[1] 25 NA 10\n\n\nVariance of one number results in NA.\n\nvar(22)\n\n[1] NA", "crumbs": [ "Analysis", "20  Missing data" @@ -1803,7 +1803,7 @@ "href": "new_pages/missing_data.html#assess-missingness-in-a-data-frame", "title": "20  Missing data", "section": "20.4 Assess missingness in a data frame", - "text": "20.4 Assess missingness in a data frame\nYou can use the package naniar to assess and visualize missingness in the data frame linelist.\n\n# install and/or load package\npacman::p_load(naniar)\n\n\nQuantifying missingness\nTo find the percent of all values that are missing use pct_miss(). Use n_miss() to get the number of missing values.\n\n# percent of ALL data frame values that are missing\npct_miss(linelist)\n\n[1] 6.688745\n\n\nThe two functions below return the percent of rows with any missing value, or that are entirely complete, respectively. Remember that NA means missing, and that `\"\" or \" \" will not be counted as missing.\n\n# Percent of rows with any value missing\npct_miss_case(linelist) # use n_complete() for counts\n\n[1] 69.12364\n\n\n\n# Percent of rows that are complete (no values missing) \npct_complete_case(linelist) # use n_complete() for counts\n\n[1] 30.87636\n\n\n\n\nVisualizing missingness\nThe gg_miss_var() function will show you the number (or %) of missing values in each column. A few nuances:\n\nYou can add a column name (not in quote) to the argument facet = to see the plot by groups\n\nBy default, counts are shown instead of percents, change this with show_pct = TRUE\n\nYou can add axis and title labels as for a normal ggplot() with + labs(...)\n\n\ngg_miss_var(linelist, show_pct = TRUE)\n\n\n\n\n\n\n\n\nHere the data are piped %>% into the function. The facet = argument is also used to split the data.\n\nlinelist %>% \n gg_miss_var(show_pct = TRUE, facet = outcome)\n\n\n\n\n\n\n\n\nYou can use vis_miss() to visualize the data frame as a heatmap, showing whether each value is missing or not. You can also select() certain columns from the data frame and provide only those columns to the function.\n\n# Heatplot of missingness across the entire data frame \nvis_miss(linelist)\n\n\n\n\n\n\n\n\n\n\nExplore and visualize missingness relationships\nHow do you visualize something that is not there??? By default, ggplot() removes points with missing values from plots.\nnaniar offers a solution via geom_miss_point(). When creating a scatterplot of two columns, records with one of the values missing and the other value present are shown by setting the missing values to 10% lower than the lowest value in the column, and coloring them distinctly.\nIn the scatterplot below, the red dots are records where the value for one column is present but the value for the other column is missing. This allows you to see the distribution of missing values in relation to the non-missing values.\n\nggplot(\n data = linelist,\n mapping = aes(x = age_years, y = temp)) + \n geom_miss_point()\n\n\n\n\n\n\n\n\nTo assess missingness in the data frame stratified by another column, consider gg_miss_fct(), which returns a heatmap of percent missingness in the data frame by a factor/categorical (or date) column:\n\ngg_miss_fct(linelist, age_cat5)\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `age_cat5 = (function (x) ...`.\nCaused by warning:\n! `fct_explicit_na()` was deprecated in forcats 1.0.0.\nℹ Please use `fct_na_value_to_level()` instead.\nℹ The deprecated feature was likely used in the naniar package.\n Please report the issue at <https://github.com/njtierney/naniar/issues>.\n\n\n\n\n\n\n\n\n\nThis function can also be used with a date column to see how missingness has changed over time:\n\ngg_miss_fct(linelist, date_onset)\n\nWarning: Removed 29 rows containing missing values or values outside the scale range\n(`geom_tile()`).\n\n\n\n\n\n\n\n\n\n\n\n“Shadow” columns\nAnother way to visualize missingness in one column by values in a second column is using the “shadow” that naniar can create. bind_shadow() creates a binary NA/not NA column for every existing column, and binds all these new columns to the original dataset with the appendix “_NA”. This doubles the number of columns - see below:\n\nshadowed_linelist <- linelist %>% \n bind_shadow()\n\nnames(shadowed_linelist)\n\n [1] \"case_id\" \"generation\" \n [3] \"date_infection\" \"date_onset\" \n [5] \"date_hospitalisation\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \n [9] \"age\" \"age_unit\" \n[11] \"age_years\" \"age_cat\" \n[13] \"age_cat5\" \"hospital\" \n[15] \"lon\" \"lat\" \n[17] \"infector\" \"source\" \n[19] \"wt_kg\" \"ht_cm\" \n[21] \"ct_blood\" \"fever\" \n[23] \"chills\" \"cough\" \n[25] \"aches\" \"vomit\" \n[27] \"temp\" \"time_admission\" \n[29] \"bmi\" \"days_onset_hosp\" \n[31] \"case_id_NA\" \"generation_NA\" \n[33] \"date_infection_NA\" \"date_onset_NA\" \n[35] \"date_hospitalisation_NA\" \"date_outcome_NA\" \n[37] \"outcome_NA\" \"gender_NA\" \n[39] \"age_NA\" \"age_unit_NA\" \n[41] \"age_years_NA\" \"age_cat_NA\" \n[43] \"age_cat5_NA\" \"hospital_NA\" \n[45] \"lon_NA\" \"lat_NA\" \n[47] \"infector_NA\" \"source_NA\" \n[49] \"wt_kg_NA\" \"ht_cm_NA\" \n[51] \"ct_blood_NA\" \"fever_NA\" \n[53] \"chills_NA\" \"cough_NA\" \n[55] \"aches_NA\" \"vomit_NA\" \n[57] \"temp_NA\" \"time_admission_NA\" \n[59] \"bmi_NA\" \"days_onset_hosp_NA\" \n\n\nThese “shadow” columns can be used to plot the proportion of values that are missing, by any another column.\nFor example, the plot below shows the proportion of records missing days_onset_hosp (number of days from symptom onset to hospitalisation), by that record’s value in date_hospitalisation. Essentially, you are plotting the density of the x-axis column, but stratifying the results (color =) by a shadow column of interest. This analysis works best if the x-axis is a numeric or date column.\n\nggplot(data = shadowed_linelist, # data frame with shadow columns\n mapping = aes(x = date_hospitalisation, # numeric or date column\n colour = age_years_NA)) + # shadow column of interest\n geom_density() # plots the density curves\n\n\n\n\n\n\n\n\nYou can also use these “shadow” columns to stratify a statistical summary, as shown below:\n\nlinelist %>%\n bind_shadow() %>% # create the shows cols\n group_by(date_outcome_NA) %>% # shadow col for stratifying\n summarise(across(\n .cols = age_years, # variable of interest for calculations\n .fns = list(\"mean\" = mean, # stats to calculate\n \"sd\" = sd,\n \"var\" = var,\n \"min\" = min,\n \"max\" = max), \n na.rm = TRUE)) # other arguments for the stat calculations\n\nWarning: There was 1 warning in `summarise()`.\nℹ In argument: `across(...)`.\nℹ In group 1: `date_outcome_NA = !NA`.\nCaused by warning:\n! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.\nSupply arguments directly to `.fns` through an anonymous function instead.\n\n # Previously\n across(a:b, mean, na.rm = TRUE)\n\n # Now\n across(a:b, \\(x) mean(x, na.rm = TRUE))\n\n\n# A tibble: 2 × 6\n date_outcome_NA age_years_mean age_years_sd age_years_var age_years_min\n <fct> <dbl> <dbl> <dbl> <dbl>\n1 !NA 16.0 12.6 158. 0\n2 NA 16.2 12.9 167. 0\n# ℹ 1 more variable: age_years_max <dbl>\n\n\nAn alternative way to plot the proportion of a column’s values that are missing over time is shown below. It does not involve naniar. This example shows percent of weekly observations that are missing).\n\nAggregate the data into a useful time unit (days, weeks, etc.), summarizing the proportion of observations with NA (and any other values of interest)\n\nPlot the proportion missing as a line using ggplot()\n\nBelow, we take the linelist, add a new column for week, group the data by week, and then calculate the percent of that week’s records where the value is missing. (note: if you want % of 7 days the calculation would be slightly different).\n\noutcome_missing <- linelist %>%\n mutate(week = lubridate::floor_date(date_onset, \"week\")) %>% # create new week column\n group_by(week) %>% # group the rows by week\n summarise( # summarize each week\n n_obs = n(), # number of records\n \n outcome_missing = sum(is.na(outcome) | outcome == \"\"), # number of records missing the value\n outcome_p_miss = outcome_missing / n_obs, # proportion of records missing the value\n \n outcome_dead = sum(outcome == \"Death\", na.rm=T), # number of records as dead\n outcome_p_dead = outcome_dead / n_obs) %>% # proportion of records as dead\n \n tidyr::pivot_longer(-week, names_to = \"statistic\") %>% # pivot all columns except week, to long format for ggplot\n filter(stringr::str_detect(statistic, \"_p_\")) # keep only the proportion values\n\nThen we plot the proportion missing as a line, by week. The ggplot basics page if you are unfamiliar with the ggplot2 plotting package.\n\nggplot(data = outcome_missing)+\n geom_line(\n mapping = aes(x = week, y = value, group = statistic, color = statistic),\n size = 2,\n stat = \"identity\")+\n labs(title = \"Weekly outcomes\",\n x = \"Week\",\n y = \"Proportion of weekly records\") + \n scale_color_discrete(\n name = \"\",\n labels = c(\"Died\", \"Missing outcome\"))+\n scale_y_continuous(breaks = c(seq(0,1,0.1)))+\n theme_minimal()+\n theme(legend.position = \"bottom\")", + "text": "20.4 Assess missingness in a data frame\nYou can use the package naniar to assess and visualize missingness in the data frame linelist.\n\n# install and/or load package\npacman::p_load(naniar)\n\n\nQuantifying missingness\nTo find the percent of all values that are missing use pct_miss(). Use n_miss() to get the number of missing values.\n\n# percent of ALL data frame values that are missing\npct_miss(linelist)\n\n[1] 6.688745\n\n\nThe two functions below return the percent of rows with any missing value, or that are entirely complete, respectively. Remember that NA means missing, and that `\"\" or \" \" will not be counted as missing.\n\n# Percent of rows with any value missing\npct_miss_case(linelist) # use n_complete() for counts\n\n[1] 69.12364\n\n\n\n# Percent of rows that are complete (no values missing) \npct_complete_case(linelist) # use n_complete() for counts\n\n[1] 30.87636\n\n\n\n\nVisualizing missingness\nThe gg_miss_var() function will show you the number (or %) of missing values in each column. A few nuances:\n\nYou can add a column name (not in quote) to the argument facet = to see the plot by groups\n\nBy default, counts are shown instead of percents, change this with show_pct = TRUE\nYou can add axis and title labels as for a normal ggplot() with + labs(...)\n\n\ngg_miss_var(linelist, show_pct = TRUE)\n\n\n\n\n\n\n\n\nHere the data are piped %>% into the function. The facet = argument is also used to split the data.\n\nlinelist %>% \n gg_miss_var(show_pct = TRUE, facet = outcome)\n\n\n\n\n\n\n\n\nYou can use vis_miss() to visualize the data frame as a heatmap, showing whether each value is missing or not. You can also select() certain columns from the data frame and provide only those columns to the function.\n\n# Heatplot of missingness across the entire data frame \nvis_miss(linelist)\n\n\n\n\n\n\n\n\n\n\nExplore and visualize missingness relationships\nHow do you visualize something that is not there? By default, ggplot() removes points with missing values from plots.\nnaniar offers a solution via geom_miss_point(). When creating a scatterplot of two columns, records with one of the values missing and the other value present are shown by setting the missing values to 10% lower than the lowest value in the column, and coloring them distinctly.\nIn the scatterplot below, the red dots are records where the value for one column is present but the value for the other column is missing. This allows you to see the distribution of missing values in relation to the non-missing values.\n\nggplot(\n data = linelist,\n mapping = aes(x = age_years, y = temp)\n ) + \n geom_miss_point()\n\n\n\n\n\n\n\n\nTo assess missingness in the data frame stratified by another column, consider gg_miss_fct(), which returns a heatmap of percent missingness in the data frame by a factor/categorical (or date) column:\n\ngg_miss_fct(linelist, age_cat5)\n\n\n\n\n\n\n\n\nThis function can also be used with a date column to see how missingness has changed over time:\n\ngg_miss_fct(linelist, date_onset)\n\nWarning: Removed 29 rows containing missing values or values outside the scale range\n(`geom_tile()`).\n\n\n\n\n\n\n\n\n\n\n\n“Shadow” columns\nAnother way to visualize missingness in one column by values in a second column is using the “shadow” that naniar can create. bind_shadow() creates a binary NA/not NA column for every existing column, and binds all these new columns to the original dataset with the appendix “_NA”. This doubles the number of columns - see below:\n\nshadowed_linelist <- linelist %>% \n bind_shadow()\n\nnames(shadowed_linelist)\n\n [1] \"case_id\" \"generation\" \n [3] \"date_infection\" \"date_onset\" \n [5] \"date_hospitalisation\" \"date_outcome\" \n [7] \"outcome\" \"gender\" \n [9] \"age\" \"age_unit\" \n[11] \"age_years\" \"age_cat\" \n[13] \"age_cat5\" \"hospital\" \n[15] \"lon\" \"lat\" \n[17] \"infector\" \"source\" \n[19] \"wt_kg\" \"ht_cm\" \n[21] \"ct_blood\" \"fever\" \n[23] \"chills\" \"cough\" \n[25] \"aches\" \"vomit\" \n[27] \"temp\" \"time_admission\" \n[29] \"bmi\" \"days_onset_hosp\" \n[31] \"case_id_NA\" \"generation_NA\" \n[33] \"date_infection_NA\" \"date_onset_NA\" \n[35] \"date_hospitalisation_NA\" \"date_outcome_NA\" \n[37] \"outcome_NA\" \"gender_NA\" \n[39] \"age_NA\" \"age_unit_NA\" \n[41] \"age_years_NA\" \"age_cat_NA\" \n[43] \"age_cat5_NA\" \"hospital_NA\" \n[45] \"lon_NA\" \"lat_NA\" \n[47] \"infector_NA\" \"source_NA\" \n[49] \"wt_kg_NA\" \"ht_cm_NA\" \n[51] \"ct_blood_NA\" \"fever_NA\" \n[53] \"chills_NA\" \"cough_NA\" \n[55] \"aches_NA\" \"vomit_NA\" \n[57] \"temp_NA\" \"time_admission_NA\" \n[59] \"bmi_NA\" \"days_onset_hosp_NA\" \n\n\nThese “shadow” columns can be used to plot the proportion of values that are missing, by any another column.\nFor example, the plot below shows the proportion of records missing days_onset_hosp (number of days from symptom onset to hospitalisation), by that record’s value in date_hospitalisation. Essentially, you are plotting the density of the x-axis column, but stratifying the results (color =) by a shadow column of interest. This analysis works best if the x-axis is a numeric or date column.\n\nggplot(data = shadowed_linelist, # data frame with shadow columns\n mapping = aes(x = date_hospitalisation, # numeric or date column\n colour = age_years_NA)) + # shadow column of interest\n geom_density() # plots the density curves\n\n\n\n\n\n\n\n\nYou can also use these “shadow” columns to stratify a statistical summary, as shown below:\n\nlinelist %>%\n bind_shadow() %>% # create the shows cols\n group_by(date_outcome_NA) %>% # shadow col for stratifying\n summarise(across(\n .cols = age_years, # variable of interest for calculations\n .fns = list(\"mean\" = mean, # stats to calculate\n \"sd\" = sd,\n \"var\" = var,\n \"min\" = min,\n \"max\" = max), \n na.rm = TRUE)) # other arguments for the stat calculations\n\nWarning: There was 1 warning in `summarise()`.\nℹ In argument: `across(...)`.\nℹ In group 1: `date_outcome_NA = !NA`.\nCaused by warning:\n! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.\nSupply arguments directly to `.fns` through an anonymous function instead.\n\n # Previously\n across(a:b, mean, na.rm = TRUE)\n\n # Now\n across(a:b, \\(x) mean(x, na.rm = TRUE))\n\n\n# A tibble: 2 × 6\n date_outcome_NA age_years_mean age_years_sd age_years_var age_years_min\n <fct> <dbl> <dbl> <dbl> <dbl>\n1 !NA 16.0 12.6 158. 0\n2 NA 16.2 12.9 167. 0\n# ℹ 1 more variable: age_years_max <dbl>\n\n\nAn alternative way to plot the proportion of a column’s values that are missing over time is shown below. It does not involve naniar. This example shows percent of weekly observations that are missing).\n\nAggregate the data into a useful time unit (days, weeks, etc.), summarizing the proportion of observations with NA (and any other values of interest)\nPlot the proportion missing as a line using ggplot()\n\nBelow, we take the linelist, add a new column for week, group the data by week, and then calculate the percent of that week’s records where the value is missing. (note: if you want % of 7 days the calculation would be slightly different).\n\noutcome_missing <- linelist %>%\n mutate(week = lubridate::floor_date(date_onset, \"week\")) %>% # create new week column\n group_by(week) %>% # group the rows by week\n summarise( # summarize each week\n n_obs = n(), # number of records\n \n outcome_missing = sum(is.na(outcome) | outcome == \"\"), # number of records missing the value\n outcome_p_miss = outcome_missing / n_obs, # proportion of records missing the value\n \n outcome_dead = sum(outcome == \"Death\", na.rm=T), # number of records as dead\n outcome_p_dead = outcome_dead / n_obs) %>% # proportion of records as dead\n \n tidyr::pivot_longer(-week, names_to = \"statistic\") %>% # pivot all columns except week, to long format for ggplot\n filter(stringr::str_detect(statistic, \"_p_\")) # keep only the proportion values\n\nThen we plot the proportion missing as a line, by week. The ggplot basics page if you are unfamiliar with the ggplot2 plotting package.\n\nggplot(data = outcome_missing)+\n geom_line(\n mapping = aes(x = week, y = value, group = statistic, color = statistic),\n size = 2,\n stat = \"identity\")+\n labs(title = \"Weekly outcomes\",\n x = \"Week\",\n y = \"Proportion of weekly records\") + \n scale_color_discrete(\n name = \"\",\n labels = c(\"Died\", \"Missing outcome\"))+\n scale_y_continuous(breaks = c(seq(0,1,0.1)))+\n theme_minimal()+\n theme(legend.position = \"bottom\")", "crumbs": [ "Analysis", "20  Missing data" @@ -1814,7 +1814,7 @@ "href": "new_pages/missing_data.html#using-data-with-missing-values", "title": "20  Missing data", "section": "20.5 Using data with missing values", - "text": "20.5 Using data with missing values\n\nFilter out rows with missing values\nTo quickly remove rows with missing values, use the dplyr function drop_na().\nThe original linelist has nrow(linelist) rows. The adjusted number of rows is shown below:\n\nlinelist %>% \n drop_na() %>% # remove rows with ANY missing values\n nrow()\n\n[1] 1818\n\n\nYou can specify to drop rows with missingness in certain columns:\n\nlinelist %>% \n drop_na(date_onset) %>% # remove rows missing date_onset \n nrow()\n\n[1] 5632\n\n\nYou can list columns one after the other, or use “tidyselect” helper functions:\n\nlinelist %>% \n drop_na(contains(\"date\")) %>% # remove rows missing values in any \"date\" column \n nrow()\n\n[1] 3029\n\n\n\n\n\nHandling NA in ggplot()\nIt is often wise to report the number of values excluded from a plot in a caption. Below is an example:\nIn ggplot(), you can add labs() and within it a caption =. In the caption, you can use str_glue() from stringr package to paste values together into a sentence dynamically so they will adjust to the data. An example is below:\n\nNote the use of \\n for a new line.\n\nNote that if multiple column would contribute to values not being plotted (e.g. age or sex if those are reflected in the plot), then you must filter on those columns as well to correctly calculate the number not shown.\n\n\nlabs(\n title = \"\",\n y = \"\",\n x = \"\",\n caption = stringr::str_glue(\n \"n = {nrow(central_data)} from Central Hospital;\n {nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown.\")) \n\nSometimes, it can be easier to save the string as an object in commands prior to the ggplot() command, and simply reference the named string object within the str_glue().\n\n\n\nNA in factors\nIf your column of interest is a factor, use fct_explicit_na() from the forcats package to convert NA values to a character value. See more detail in the Factors page. By default, the new value is “(Missing)” but this can be adjusted via the na_level = argument.\n\npacman::p_load(forcats) # load package\n\nlinelist <- linelist %>% \n mutate(gender = fct_explicit_na(gender, na_level = \"Missing\"))\n\nlevels(linelist$gender)\n\n[1] \"f\" \"m\" \"Missing\"", + "text": "20.5 Using data with missing values\n\nFilter out rows with missing values\nTo quickly remove rows with missing values, use the dplyr function drop_na().\nThe original linelist has nrow(linelist) rows. The adjusted number of rows is shown below:\n\nlinelist %>% \n drop_na() %>% # remove rows with ANY missing values\n nrow()\n\n[1] 1818\n\n\nYou can specify to drop rows with missingness in certain columns:\n\nlinelist %>% \n drop_na(date_onset) %>% # remove rows missing date_onset \n nrow()\n\n[1] 5632\n\n\nYou can list columns one after the other, or use “tidyselect” helper functions:\n\nlinelist %>% \n drop_na(contains(\"date\")) %>% # remove rows missing values in any \"date\" column \n nrow()\n\n[1] 3029\n\n\n\n\n\nHandling NA in ggplot()\nIt is often wise to report the number of values excluded from a plot in a caption. Below is an example:\nIn ggplot(), you can add labs() and within it a caption =. In the caption, you can use str_glue() from stringr package to paste values together into a sentence dynamically so they will adjust to the data. An example is below:\n\nNote the use of \\n for a new line\nNote that if multiple column would contribute to values not being plotted (e.g. age or sex if those are reflected in the plot), then you must filter on those columns as well to correctly calculate the number not shown\n\n\nlabs(\n title = \"\",\n y = \"\",\n x = \"\",\n caption = stringr::str_glue(\n \"n = {nrow(central_data)} from Central Hospital;\n {nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown.\")\n ) \n\nSometimes, it can be easier to save the string as an object in commands prior to the ggplot() command, and simply reference the named string object within the str_glue().\n\n\n\nNA in factors\nIf your column of interest is a factor, use fct_explicit_na() from the forcats package to convert NA values to a character value. See more detail in the Factors page. By default, the new value is “(Missing)” but this can be adjusted via the na_level = argument.\n\npacman::p_load(forcats) # load package\n\nlinelist <- linelist %>% \n mutate(gender = fct_explicit_na(gender, na_level = \"Missing\"))\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `gender = fct_explicit_na(gender, na_level = \"Missing\")`.\nCaused by warning:\n! `fct_explicit_na()` was deprecated in forcats 1.0.0.\nℹ Please use `fct_na_value_to_level()` instead.\n\nlevels(linelist$gender)\n\n[1] \"f\" \"m\" \"Missing\"", "crumbs": [ "Analysis", "20  Missing data" @@ -1825,7 +1825,7 @@ "href": "new_pages/missing_data.html#imputation", "title": "20  Missing data", "section": "20.6 Imputation", - "text": "20.6 Imputation\nSometimes, when analyzing your data, it will be important to “fill in the gaps” and impute missing data While you can always simply analyze a dataset after removing all missing values, this can cause problems in many ways. Here are two examples:\n\nBy removing all observations with missing values or variables with a large amount of missing data, you might reduce your power or ability to do some types of analysis. For example, as we discovered earlier, only a small fraction of the observations in our linelist dataset have no missing data across all of our variables. If we removed the majority of our dataset we’d be losing a lot of information! And, most of our variables have some amount of missing data–for most analysis it’s probably not reasonable to drop every variable that has a lot of missing data either.\nDepending on why your data is missing, analysis of only non-missing data might lead to biased or misleading results. For example, as we learned earlier we are missing data for some patients about whether they’ve had some important symptoms like fever or cough. But, as one possibility, maybe that information wasn’t recorded for people that just obviously weren’t very sick. In that case, if we just removed these observations we’d be excluding some of the healthiest people in our dataset and that might really bias any results.\n\nIt’s important to think about why your data might be missing in addition to seeing how much is missing. Doing this can help you decide how important it might be to impute missing data, and also which method of imputing missing data might be best in your situation.\n\nTypes of missing data\nHere are three general types of missing data:\n\nMissing Completely at Random (MCAR). This means that there is no relationship between the probability of data being missing and any of the other variables in your data. The probability of being missing is the same for all cases This is a rare situation. But, if you have strong reason to believe your data is MCAR analyzing only non-missing data without imputing won’t bias your results (although you may lose some power). [TODO: consider discussing statistical tests for MCAR]\nMissing at Random (MAR). This name is actually a bit misleading as MAR means that your data is missing in a systematic, predictable way based on the other information you have. For example, maybe every observation in our dataset with a missing value for fever was actually not recorded because every patient with chills and and aches was just assumed to have a fever so their temperature was never taken. If true, we could easily predict that every missing observation with chills and aches has a fever as well and use this information to impute our missing data. In practice, this is more of a spectrum. Maybe if a patient had both chills and aches they were more likely to have a fever as well if they didn’t have their temperature taken, but not always. This is still predictable even if it isn’t perfectly predictable. This is a common type of missing data\nMissing not at Random (MNAR). Sometimes, this is also called Not Missing at Random (NMAR). This assumes that the probability of a value being missing is NOT systematic or predictable using the other information we have but also isn’t missing randomly. In this situation data is missing for unknown reasons or for reasons you don’t have any information about. For example, in our dataset maybe information on age is missing because some very elderly patients either don’t know or refuse to say how old they are. In this situation, missing data on age is related to the value itself (and thus isn’t random) and isn’t predictable based on the other information we have. MNAR is complex and often the best way of dealing with this is to try to collect more data or information about why the data is missing rather than attempt to impute it.\n\nIn general, imputing MCAR data is often fairly simple, while MNAR is very challenging if not impossible. Many of the common data imputation methods assume MAR.\n\n\nUseful packages\nSome useful packages for imputing missing data are Mmisc, missForest (which uses random forests to impute missing data), and mice (Multivariate Imputation by Chained Equations). For this section we’ll just use the mice package, which implements a variety of techniques. The maintainer of the mice package has published an online book about imputing missing data that goes into more detail here (https://stefvanbuuren.name/fimd/).\nHere is the code to load the mice package:\n\npacman::p_load(mice)\n\n\n\nMean Imputation\nSometimes if you are doing a simple analysis or you have strong reason to think you can assume MCAR, you can simply set missing numerical values to the mean of that variable. Perhaps we can assume that missing temperature measurements in our dataset were either MCAR or were just normal values. Here is the code to create a new variable that replaces missing temperature values with the mean temperature value in our dataset. However, in many situations replacing data with the mean can lead to bias, so be careful.\n\nlinelist <- linelist %>%\n mutate(temp_replace_na_with_mean = replace_na(temp, mean(temp, na.rm = T)))\n\nYou could also do a similar process for replacing categorical data with a specific value. For our dataset, imagine you knew that all observations with a missing value for their outcome (which can be “Death” or “Recover”) were actually people that died (note: this is not actually true for this dataset):\n\nlinelist <- linelist %>%\n mutate(outcome_replace_na_with_death = replace_na(outcome, \"Death\"))\n\n\n\nRegression imputation\nA somewhat more advanced method is to use some sort of statistical model to predict what a missing value is likely to be and replace it with the predicted value. Here is an example of creating predicted values for all the observations where temperature is missing, but age and fever are not, using simple linear regression using fever status and age in years as predictors. In practice you’d want to use a better model than this sort of simple approach.\n\nsimple_temperature_model_fit <- lm(temp ~ fever + age_years, data = linelist)\n\n#using our simple temperature model to predict values just for the observations where temp is missing\npredictions_for_missing_temps <- predict(simple_temperature_model_fit,\n newdata = linelist %>% filter(is.na(temp))) \n\nOr, using the same modeling approach through the mice package to create imputed values for the missing temperature observations:\n\nmodel_dataset <- linelist %>%\n select(temp, fever, age_years) \n\ntemp_imputed <- mice(model_dataset,\n method = \"norm.predict\",\n seed = 1,\n m = 1,\n print = F)\n\nWarning: Number of logged events: 1\n\ntemp_imputed_values <- temp_imputed$imp$temp\n\nThis is the same type of approach by some more advanced methods like using the missForest package to replace missing data with predicted values. In that case, the prediction model is a random forest instead of a linear regression. You can use other types of models to do this as well. However, while this approach works well under MCAR you should be a bit careful if you believe MAR or MNAR more accurately describes your situation. The quality of your imputation will depend on how good your prediction model is and even with a very good model the variability of your imputed data may be underestimated.\n\n\nLOCF and BOCF\nLast observation carried forward (LOCF) and baseline observation carried forward (BOCF) are imputation methods for time series/longitudinal data. The idea is to take the previous observed value as a replacement for the missing data. When multiple values are missing in succession, the method searches for the last observed value.\nThe fill() function from the tidyr package can be used for both LOCF and BOCF imputation (however, other packages such as HMISC, zoo, and data.table also include methods for doing this). To show the fill() syntax we’ll make up a simple time series dataset containing the number of cases of a disease for each quarter of the years 2000 and 2001. However, the year value for subsequent quarters after Q1 are missing so we’ll need to impute them. The fill() junction is also demonstrated in the Pivoting data page.\n\n#creating our simple dataset\ndisease <- tibble::tribble(\n ~quarter, ~year, ~cases,\n \"Q1\", 2000, 66013,\n \"Q2\", NA, 69182,\n \"Q3\", NA, 53175,\n \"Q4\", NA, 21001,\n \"Q1\", 2001, 46036,\n \"Q2\", NA, 58842,\n \"Q3\", NA, 44568,\n \"Q4\", NA, 50197)\n\n#imputing the missing year values:\ndisease %>% fill(year)\n\n# A tibble: 8 × 3\n quarter year cases\n <chr> <dbl> <dbl>\n1 Q1 2000 66013\n2 Q2 2000 69182\n3 Q3 2000 53175\n4 Q4 2000 21001\n5 Q1 2001 46036\n6 Q2 2001 58842\n7 Q3 2001 44568\n8 Q4 2001 50197\n\n\nNote: make sure your data are sorted correctly before using the fill() function. fill() defaults to filling “down” but you can also impute values in different directions by changing the .direction parameter. We can make a similar dataset where the year value is recorded only at the end of the year and missing for earlier quarters:\n\n#creating our slightly different dataset\ndisease <- tibble::tribble(\n ~quarter, ~year, ~cases,\n \"Q1\", NA, 66013,\n \"Q2\", NA, 69182,\n \"Q3\", NA, 53175,\n \"Q4\", 2000, 21001,\n \"Q1\", NA, 46036,\n \"Q2\", NA, 58842,\n \"Q3\", NA, 44568,\n \"Q4\", 2001, 50197)\n\n#imputing the missing year values in the \"up\" direction:\ndisease %>% fill(year, .direction = \"up\")\n\n# A tibble: 8 × 3\n quarter year cases\n <chr> <dbl> <dbl>\n1 Q1 2000 66013\n2 Q2 2000 69182\n3 Q3 2000 53175\n4 Q4 2000 21001\n5 Q1 2001 46036\n6 Q2 2001 58842\n7 Q3 2001 44568\n8 Q4 2001 50197\n\n\nIn this example, LOCF and BOCF are clearly the right things to do, but in more complicated situations it may be harder to decide if these methods are appropriate. For example, you may have missing laboratory values for a hospital patient after the first day. Sometimes, this can mean the lab values didn’t change…but it could also mean the patient recovered and their values would be very different after the first day! Use these methods with caution.\n\n\nMultiple Imputation\nThe online book we mentioned earlier by the author of the mice package (https://stefvanbuuren.name/fimd/) contains a detailed explanation of multiple imputation and why you’d want to use it. But, here is a basic explanation of the method:\nWhen you do multiple imputation, you create multiple datasets with the missing values imputed to plausible data values (depending on your research data you might want to create more or less of these imputed datasets, but the mice package sets the default number to 5). The difference is that rather than a single, specific value each imputed value is drawn from an estimated distribution (so it includes some randomness). As a result, each of these datasets will have slightly different different imputed values (however, the non-missing data will be the same in each of these imputed datasets). You still use some sort of predictive model to do the imputation in each of these new datasets (mice has many options for prediction methods including Predictive Mean Matching, logistic regression, and random forest) but the mice package can take care of many of the modeling details.\nThen, once you have created these new imputed datasets, you can apply then apply whatever statistical model or analysis you were planning to do for each of these new imputed datasets and pool the results of these models together. This works very well to reduce bias in both MCAR and many MAR settings and often results in more accurate standard error estimates.\nHere is an example of applying the Multiple Imputation process to predict temperature in our linelist dataset using a age and fever status (our simplified model_dataset from above):\n\n# imputing missing values for all variables in our model_dataset, and creating 10 new imputed datasets\nmultiple_imputation = mice(\n model_dataset,\n seed = 1,\n m = 10,\n print = FALSE) \n\nWarning: Number of logged events: 1\n\nmodel_fit <- with(multiple_imputation, lm(temp ~ age_years + fever))\n\nbase::summary(mice::pool(model_fit))\n\n term estimate std.error statistic df p.value\n1 (Intercept) 3.703143e+01 0.0270863456 1.367162e+03 26.83673 1.583113e-66\n2 age_years 3.867829e-05 0.0006090202 6.350905e-02 171.44363 9.494351e-01\n3 feveryes 1.978044e+00 0.0193587115 1.021785e+02 176.51325 5.666771e-159\n\n\nHere we used the mice default method of imputation, which is Predictive Mean Matching. We then used these imputed datasets to separately estimate and then pool results from simple linear regressions on each of these datasets. There are many details we’ve glossed over and many settings you can adjust during the Multiple Imputation process while using the mice package. For example, you won’t always have numerical data and might need to use other imputation methods (you can still use the mice package for many other types of data and methods). But, for a more robust analysis when missing data is a significant concern, Multiple Imputation is good solution that isn’t always much more work than doing a complete case analysis.", + "text": "20.6 Imputation\nSometimes, when analyzing your data, it will be important to “fill in the gaps” and impute missing data While you can always simply analyze a dataset after removing all missing values, this can cause problems in many ways. Here are two examples:\n\nBy removing all observations with missing values or variables with a large amount of missing data, you might reduce your power or ability to do some types of analysis. For example, as we discovered earlier, only a small fraction of the observations in our linelist dataset have no missing data across all of our variables. If we removed the majority of our dataset we’d be losing a lot of information! And, most of our variables have some amount of missing data–for most analysis it’s probably not reasonable to drop every variable that has a lot of missing data either.\nDepending on why your data is missing, analysis of only non-missing data might lead to biased or misleading results. For example, as we learned earlier we are missing data for some patients about whether they’ve had some important symptoms like fever or cough. But, as one possibility, maybe that information wasn’t recorded for people that just obviously weren’t very sick. In that case, if we just removed these observations we’d be excluding some of the healthiest people in our dataset and that might really bias any results.\n\nIt’s important to think about why your data might be missing in addition to seeing how much is missing. Doing this can help you decide how important it might be to impute missing data, and also which method of imputing missing data might be best in your situation.\n\nTypes of missing data\nHere are three general types of missing data:\n\nMissing Completely at Random (MCAR). This means that there is no relationship between the probability of data being missing and any of the other variables in your data. The probability of being missing is the same for all cases This is a rare situation. But, if you have strong reason to believe your data is MCAR analyzing only non-missing data without imputing won’t bias your results (although you may lose some power).\n\nTo test if your data is MCAR, you can use mcar_test from the naniar package. Note this will only work with numerical values, so you may need to convert your data before running the test.\nIf the p-value is less than or equal to 0.05, then your data is not missing completely at random.\n\n## define variables of interest \nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n#Recode linelist to replace characters with numeric\nlinelist <- linelist %>% \n mutate(across( \n .cols = all_of(c(explanatory_vars, \"outcome\")), # for each column listed and \"outcome\"\n .fns = ~case_when( \n . %in% c(\"m\", \"yes\", \"Death\") ~ 1, # recode male, yes and death to 1\n . %in% c(\"f\", \"no\", \"Recover\") ~ 0, # female, no and recover to 0\n TRUE ~ NA_real_) # otherwise set to missing\n )\n )\n\nlinelist %>%\n select(explanatory_vars, \"outcome\") %>%\n mcar_test()\n\nWarning: Using an external vector in selections was deprecated in tidyselect 1.1.0.\nℹ Please use `all_of()` or `any_of()` instead.\n # Was:\n data %>% select(explanatory_vars)\n\n # Now:\n data %>% select(all_of(explanatory_vars))\n\nSee <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.\n\n\n# A tibble: 1 × 4\n statistic df p.value missing.patterns\n <dbl> <dbl> <dbl> <int>\n1 27.9 21 0.144 7\n\n\nAs the p-value is greater than 0.05, we have failed to reject the null hypothesis (that the data is MCAR), and so no patterns exist in the missing data.\n\nMissing at Random (MAR). This name is actually a bit misleading as MAR means that your data is missing in a systematic, predictable way based on the other information you have. For example, maybe every observation in our dataset with a missing value for fever was actually not recorded because every patient with chills and and aches was just assumed to have a fever so their temperature was never taken. If true, we could easily predict that every missing observation with chills and aches has a fever as well and use this information to impute our missing data. In practice, this is more of a spectrum. Maybe if a patient had both chills and aches they were more likely to have a fever as well if they didn’t have their temperature taken, but not always. This is still predictable even if it isn’t perfectly predictable. This is a common type of missing data.\nMissing not at Random (MNAR). Sometimes, this is also called Not Missing at Random (NMAR). This assumes that the probability of a value being missing is NOT systematic or predictable using the other information we have but also isn’t missing randomly. In this situation data is missing for unknown reasons or for reasons you don’t have any information about. For example, in our dataset maybe information on age is missing because some very elderly patients either don’t know or refuse to say how old they are. In this situation, missing data on age is related to the value itself (and thus isn’t random) and isn’t predictable based on the other information we have. MNAR is complex and often the best way of dealing with this is to try to collect more data or information about why the data is missing rather than attempt to impute it.\n\nIn general, imputing MCAR data is often fairly simple, while MNAR is very challenging if not impossible. Many of the common data imputation methods assume MAR.\n\n\nUseful packages\nSome useful packages for imputing missing data are Mmisc, missForest (which uses random forests to impute missing data), and mice (Multivariate Imputation by Chained Equations). For this section we’ll just use the mice package, which implements a variety of techniques. The maintainer of the mice package has published an online book about imputing missing data that goes into more detail here (https://stefvanbuuren.name/fimd/).\nHere is the code to load the mice package:\n\npacman::p_load(mice)\n\n\n\nMean Imputation\nSometimes if you are doing a simple analysis or you have strong reason to think you can assume MCAR, you can simply set missing numerical values to the mean of that variable. Perhaps we can assume that missing temperature measurements in our dataset were either MCAR or were just normal values. Here is the code to create a new variable that replaces missing temperature values with the mean temperature value in our dataset. However, in many situations replacing data with the mean can lead to bias, so be careful.\n\nlinelist <- linelist %>%\n mutate(temp_replace_na_with_mean = replace_na(temp, mean(temp, na.rm = T)))\n\nYou could also do a similar process for replacing categorical data with a specific value. For our dataset, imagine you knew that all observations with a missing value for their outcome (which can be “Death” or “Recover”) were actually people that died (note: this is not actually true for this dataset):\n\nlinelist <- linelist %>%\n mutate(outcome_replace_na_with_death = replace_na(outcome, 1))\n\n\n\nRegression imputation\nA somewhat more advanced method is to use some sort of statistical model to predict what a missing value is likely to be and replace it with the predicted value. Here is an example of creating predicted values for all the observations where temperature is missing, but age and fever are not, using simple linear regression using fever status and age in years as predictors. In practice you’d want to use a better model than this sort of simple approach.\n\nsimple_temperature_model_fit <- lm(temp ~ fever + age_years, data = linelist)\n\n#using our simple temperature model to predict values just for the observations where temp is missing\npredictions_for_missing_temps <- predict(simple_temperature_model_fit,\n newdata = linelist %>% filter(is.na(temp))) \n\nOr, using the same modeling approach through the mice package to create imputed values for the missing temperature observations:\n\nmodel_dataset <- linelist %>%\n select(temp, fever, age_years) \n\ntemp_imputed <- mice(model_dataset,\n method = \"norm.predict\",\n seed = 1,\n m = 1,\n print = F)\n\ntemp_imputed_values <- temp_imputed$imp$temp\n\nThis is the same type of approach by some more advanced methods like using the missForest package to replace missing data with predicted values. In that case, the prediction model is a random forest instead of a linear regression. You can use other types of models to do this as well. However, while this approach works well under MCAR you should be a bit careful if you believe MAR or MNAR more accurately describes your situation. The quality of your imputation will depend on how good your prediction model is and even with a very good model the variability of your imputed data may be underestimated.\n\n\nLOCF and BOCF\nLast observation carried forward (LOCF) and baseline observation carried forward (BOCF) are imputation methods for time series/longitudinal data. The idea is to take the previous observed value as a replacement for the missing data. When multiple values are missing in succession, the method searches for the last observed value.\nThe fill() function from the tidyr package can be used for both LOCF and BOCF imputation (however, other packages such as HMISC, zoo, and data.table also include methods for doing this). To show the fill() syntax we’ll make up a simple time series dataset containing the number of cases of a disease for each quarter of the years 2000 and 2001. However, the year value for subsequent quarters after Q1 are missing so we’ll need to impute them. The fill() junction is also demonstrated in the Pivoting data page.\n\n#creating our simple dataset\ndisease <- tibble::tribble(\n ~quarter, ~year, ~cases,\n \"Q1\", 2000, 66013,\n \"Q2\", NA, 69182,\n \"Q3\", NA, 53175,\n \"Q4\", NA, 21001,\n \"Q1\", 2001, 46036,\n \"Q2\", NA, 58842,\n \"Q3\", NA, 44568,\n \"Q4\", NA, 50197)\n\n#imputing the missing year values:\ndisease %>% fill(year)\n\n# A tibble: 8 × 3\n quarter year cases\n <chr> <dbl> <dbl>\n1 Q1 2000 66013\n2 Q2 2000 69182\n3 Q3 2000 53175\n4 Q4 2000 21001\n5 Q1 2001 46036\n6 Q2 2001 58842\n7 Q3 2001 44568\n8 Q4 2001 50197\n\n\nNote: make sure your data are sorted correctly before using the fill() function. fill() defaults to filling “down” but you can also impute values in different directions by changing the .direction parameter. We can make a similar dataset where the year value is recorded only at the end of the year and missing for earlier quarters:\n\n#creating our slightly different dataset\ndisease <- tibble::tribble(\n ~quarter, ~year, ~cases,\n \"Q1\", NA, 66013,\n \"Q2\", NA, 69182,\n \"Q3\", NA, 53175,\n \"Q4\", 2000, 21001,\n \"Q1\", NA, 46036,\n \"Q2\", NA, 58842,\n \"Q3\", NA, 44568,\n \"Q4\", 2001, 50197)\n\n#imputing the missing year values in the \"up\" direction:\ndisease %>% fill(year, .direction = \"up\")\n\n# A tibble: 8 × 3\n quarter year cases\n <chr> <dbl> <dbl>\n1 Q1 2000 66013\n2 Q2 2000 69182\n3 Q3 2000 53175\n4 Q4 2000 21001\n5 Q1 2001 46036\n6 Q2 2001 58842\n7 Q3 2001 44568\n8 Q4 2001 50197\n\n\nIn this example, LOCF and BOCF are clearly the right things to do, but in more complicated situations it may be harder to decide if these methods are appropriate. For example, you may have missing laboratory values for a hospital patient after the first day. Sometimes, this can mean the lab values didn’t change…but it could also mean the patient recovered and their values would be very different after the first day!\nUse these methods with caution.\n\n\nMultiple Imputation\nThe online book we mentioned earlier by the author of the mice package (https://stefvanbuuren.name/fimd/) contains a detailed explanation of multiple imputation and why you’d want to use it. But, here is a basic explanation of the method:\nWhen you do multiple imputation, you create multiple datasets with the missing values imputed to plausible data values (depending on your research data you might want to create more or less of these imputed datasets, but the mice package sets the default number to 5). The difference is that rather than a single, specific value each imputed value is drawn from an estimated distribution (so it includes some randomness). As a result, each of these datasets will have slightly different different imputed values (however, the non-missing data will be the same in each of these imputed datasets). You still use some sort of predictive model to do the imputation in each of these new datasets (mice has many options for prediction methods including Predictive Mean Matching, logistic regression, and random forest) but the mice package can take care of many of the modeling details.\nThen, once you have created these new imputed datasets, you can apply then apply whatever statistical model or analysis you were planning to do for each of these new imputed datasets and pool the results of these models together. This works very well to reduce bias in both MCAR and many MAR settings and often results in more accurate standard error estimates.\nHere is an example of applying the Multiple Imputation process to predict temperature in our linelist dataset using a age and fever status (our simplified model_dataset from above):\n\n# imputing missing values for all variables in our model_dataset, and creating 10 new imputed datasets\nmultiple_imputation = mice(\n model_dataset,\n seed = 1,\n m = 10,\n print = FALSE\n ) \n\nmodel_fit <- with(multiple_imputation, lm(temp ~ age_years + fever))\n\nbase::summary(mice::pool(model_fit))\n\n term estimate std.error statistic df p.value\n1 (Intercept) 3.696900e+01 0.0154162569 2398.0527445 2176.7305 0.0000000\n2 age_years 4.481642e-04 0.0005023468 0.8921411 1438.8255 0.3724665\n3 fever 2.044250e+00 0.0153006140 133.6057671 953.2349 0.0000000\n\n\nHere we used the mice default method of imputation, which is Predictive Mean Matching. We then used these imputed datasets to separately estimate and then pool results from simple linear regressions on each of these datasets. There are many details we’ve glossed over and many settings you can adjust during the Multiple Imputation process while using the mice package. For example, you won’t always have numerical data and might need to use other imputation methods (you can still use the mice package for many other types of data and methods). But, for a more robust analysis when missing data is a significant concern, Multiple Imputation is good solution that isn’t always much more work than doing a complete case analysis.", "crumbs": [ "Analysis", "20  Missing data" @@ -1869,7 +1869,7 @@ "href": "new_pages/standardization.html#preparation", "title": "21  Standardised rates", "section": "21.2 Preparation", - "text": "21.2 Preparation\nTo show how standardization is done, we will use fictitious population counts and death counts from country A and country B, by age (in 5 year categories) and sex (female, male). To make the datasets ready for use, we will perform the following preparation steps:\n\nLoad packages\n\nLoad datasets\n\nJoin the population and death data from the two countries\nPivot longer so there is one row per age-sex stratum\nClean the reference population (world standard population) and join it to the country data\n\nIn your scenario, your data may come in a different format. Perhaps your data are by province, city, or other catchment area. You may have one row for each death and information on age and sex for each (or a significant proportion) of these deaths. In this case, see the pages on Grouping data, Pivoting data, and Descriptive tables to create a dataset with event and population counts per age-sex stratum.\nWe also need a reference population, the standard population. For the purposes of this exercise we will use the world_standard_population_by_sex. The World standard population is based on the populations of 46 countries and was developed in 1960. There are many “standard” populations - as one example, the website of NHS Scotland is quite informative on the European Standard Population, World Standard Population and Scotland Standard Population.\n\n\nLoad packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export data\n here, # locate files\n stringr, # cleaning characters and strings\n frailtypack, # needed for dsr, for frailty models\n dsr, # standardise rates\n PHEindicatormethods, # alternative for rate standardisation\n tidyverse) # data management and visualization\n\nCAUTION: If you have a newer version of R, the dsr package cannot be directly downloaded from CRAN. However, it is still available from the CRAN archive. You can install and use this one. \nFor non-Mac users:\n\npackageurl <- \"https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz\"\ninstall.packages(packageurl, repos=NULL, type=\"source\")\n\n\n# Other solution that may work\nrequire(devtools)\ndevtools::install_version(\"dsr\", version=\"0.2.2\", repos=\"http:/cran.us.r.project.org\")\n\nFor Mac users:\n\nrequire(devtools)\ndevtools::install_version(\"dsr\", version=\"0.2.2\", repos=\"https://mac.R-project.org\")\n\n\n\nLoad population data\nSee the Download handbook and data page for instructions on how to download all the example data in the handbook. You can import the Standardisation page data directly into R from our Github repository by running the following import() commands:\n\n# import demographics for country A directly from Github\nA_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics.csv\")\n\n# import deaths for country A directly from Github\nA_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryA.csv\")\n\n# import demographics for country B directly from Github\nB_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics_2.csv\")\n\n# import deaths for country B directly from Github\nB_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryB.csv\")\n\n# import demographics for country B directly from Github\nstandard_pop_data <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/world_standard_population_by_sex.csv\")\n\nFirst we load the demographic data (counts of males and females by 5-year age category) for the two countries that we will be comparing, “Country A” and “Country B”.\n\n# Country A\nA_demo <- import(\"country_demographics.csv\")\n\n\n\n\n\n\n\n\n# Country B\nB_demo <- import(\"country_demographics_2.csv\")\n\n\n\n\n\n\n\n\n\nLoad death counts\nConveniently, we also have the counts of deaths during the time period of interest, by age and sex. Each country’s counts are in a separate file, shown below.\nDeaths in Country A\n\n\n\n\n\n\nDeaths in Country B\n\n\n\n\n\n\n\n\nClean populations and deaths\nWe need to join and transform these data in the following ways:\n\nCombine country populations into one dataset and pivot “long” so that each age-sex stratum is one row\n\nCombine country death counts into one dataset and pivot “long” so each age-sex stratum is one row\n\nJoin the deaths to the populations\n\nFirst, we combine the country populations datasets, pivot longer, and do minor cleaning. See the page on Pivoting data for more detail.\n\npop_countries <- A_demo %>% # begin with country A dataset\n bind_rows(B_demo) %>% # bind rows, because cols are identically named\n pivot_longer( # pivot longer\n cols = c(m, f), # columns to combine into one\n names_to = \"Sex\", # name for new column containing the category (\"m\" or \"f\") \n values_to = \"Population\") %>% # name for new column containing the numeric values pivoted\n mutate(Sex = recode(Sex, # re-code values for clarity\n \"m\" = \"Male\",\n \"f\" = \"Female\"))\n\nThe combined population data now look like this (click through to see countries A and B):\n\n\n\n\n\n\nAnd now we perform similar operations on the two deaths datasets.\n\ndeaths_countries <- A_deaths %>% # begin with country A deaths dataset\n bind_rows(B_deaths) %>% # bind rows with B dataset, because cols are identically named\n pivot_longer( # pivot longer\n cols = c(Male, Female), # column to transform into one\n names_to = \"Sex\", # name for new column containing the category (\"m\" or \"f\") \n values_to = \"Deaths\") %>% # name for new column containing the numeric values pivoted\n rename(age_cat5 = AgeCat) # rename for clarity\n\nThe deaths data now look like this, and contain data from both countries:\n\n\n\n\n\n\nWe now join the deaths and population data based on common columns Country, age_cat5, and Sex. This adds the column Deaths.\n\ncountry_data <- pop_countries %>% \n left_join(deaths_countries, by = c(\"Country\", \"age_cat5\", \"Sex\"))\n\nWe can now classify Sex, age_cat5, and Country as factors and set the level order using fct_relevel() function from the forcats package, as described in the page on Factors. Note, classifying the factor levels doesn’t visibly change the data, but the arrange() command does sort it by Country, age category, and sex.\n\ncountry_data <- country_data %>% \n mutate(\n Country = fct_relevel(Country, \"A\", \"B\"),\n \n Sex = fct_relevel(Sex, \"Male\", \"Female\"),\n \n age_cat5 = fct_relevel(\n age_cat5,\n \"0-4\", \"5-9\", \"10-14\", \"15-19\",\n \"20-24\", \"25-29\", \"30-34\", \"35-39\",\n \"40-44\", \"45-49\", \"50-54\", \"55-59\",\n \"60-64\", \"65-69\", \"70-74\",\n \"75-79\", \"80-84\", \"85\")) %>% \n \n arrange(Country, age_cat5, Sex)\n\n\n\n\n\n\n\nCAUTION: If you have few deaths per stratum, consider using 10-, or 15-year categories, instead of 5-year categories for age.\n\n\nLoad reference population\nLastly, for the direct standardisation, we import the reference population (world “standard population” by sex)\n\n# Reference population\nstandard_pop_data <- import(\"world_standard_population_by_sex.csv\")\n\n\n\n\n\n\n\n\n\n\nClean reference population\nThe age category values in the country_data and standard_pop_data data frames will need to be aligned.\nCurrently, the values of the column age_cat5 from the standard_pop_data data frame contain the word “years” and “plus”, while those of the country_data data frame do not. We will have to make the age category values match. We use str_replace_all() from the stringr package, as described in the page on Characters and strings, to replace these patterns with no space \"\".\nFurthermore, the package dsr expects that in the standard population, the column containing counts will be called \"pop\". So we rename that column accordingly.\n\n# Remove specific string from column values\nstandard_pop_clean <- standard_pop_data %>%\n mutate(\n age_cat5 = str_replace_all(age_cat5, \"years\", \"\"), # remove \"year\"\n age_cat5 = str_replace_all(age_cat5, \"plus\", \"\"), # remove \"plus\"\n age_cat5 = str_replace_all(age_cat5, \" \", \"\")) %>% # remove \" \" space\n \n rename(pop = WorldStandardPopulation) # change col name to \"pop\", as this is expected by dsr package\n\nCAUTION: If you try to use str_replace_all() to remove a plus symbol, it won’t work because it is a special symbol. “Escape” the specialnes by putting two back slashes in front, as in str_replace_call(column, \"\\\\+\", \"\"). \n\n\nCreate dataset with standard population\nFinally, the package PHEindicatormethods, detailed below, expects the standard populations joined to the country event and population counts. So, we will create a dataset all_data for that purpose.\n\nall_data <- left_join(country_data, standard_pop_clean, by=c(\"age_cat5\", \"Sex\"))\n\nThis complete dataset looks like this:", + "text": "21.2 Preparation\nTo show how standardization is done, we will use fictitious population counts and death counts from country A and country B, by age (in 5 year categories) and sex (female, male). To make the datasets ready for use, we will perform the following preparation steps:\n\nLoad packages\nImport datasets\n\nJoin the population and death data from the two countries\nPivot longer so there is one row per age-sex stratum\nClean the reference population (world standard population) and join it to the country data\n\nIn your scenario, your data may come in a different format. Perhaps your data are by province, city, or other catchment area. You may have one row for each death and information on age and sex for each (or a significant proportion) of these deaths. In this case, see the pages on Grouping data, Pivoting data, and Descriptive tables to create a dataset with event and population counts per age-sex stratum.\nWe also need a reference population, the standard population. For the purposes of this exercise we will use the world_standard_population_by_sex. The World standard population is based on the populations of 46 countries and was developed in 1960. There are many “standard” populations - as one example, the website of NHS Scotland is quite informative on the European Standard Population, World Standard Population and Scotland Standard Population.\n\n\nLoad packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export data\n here, # locate files\n stringr, # cleaning characters and strings\n PHEindicatormethods, # alternative for rate standardisation\n tidyverse # data management and visualization\n)\n\n\n\nLoad population data\nSee the Download handbook and data page for instructions on how to download all the example data in the handbook. You can import the Standardisation page data directly into R from our Github repository by running the following import() commands:\n\n# import demographics for country A directly from Github\nA_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics.csv\")\n\n# import deaths for country A directly from Github\nA_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryA.csv\")\n\n# import demographics for country B directly from Github\nB_demo <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics_2.csv\")\n\n# import deaths for country B directly from Github\nB_deaths <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryB.csv\")\n\n# import demographics for country B directly from Github\nstandard_pop_data <- import(\"https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/world_standard_population_by_sex.csv\")\n\nFirst we load the demographic data (counts of males and females by 5-year age category) for the two countries that we will be comparing, “Country A” and “Country B”.\n\n# Country A\nA_demo <- import(\"country_demographics.csv\")\n\n\n\n\n\n\n\n\n# Country B\nB_demo <- import(\"country_demographics_2.csv\")\n\n\n\n\n\n\n\n\n\nLoad death counts\nConveniently, we also have the counts of deaths during the time period of interest, by age and sex. Each country’s counts are in a separate file, shown below.\nDeaths in Country A\n\n\n\n\n\n\nDeaths in Country B\n\n\n\n\n\n\n\n\nClean populations and deaths\nWe need to join and transform these data in the following ways:\n\nCombine country populations into one dataset and pivot “long” so that each age-sex stratum is one row.\n\nCombine country death counts into one dataset and pivot “long” so each age-sex stratum is one row.\n\nJoin the deaths to the populations.\n\nFirst, we combine the country populations datasets, pivot longer, and do minor cleaning. See the page on Pivoting data for more detail.\n\npop_countries <- A_demo %>% # begin with country A dataset\n mutate(Country = \"A\") %>% # add in a country identifier\n bind_rows(B_demo %>% # bind rows, because cols are identically named\n mutate(Country = \"B\")) %>% # add in country identifier\n pivot_longer( # pivot longer\n cols = c(m, f), # columns to combine into one\n names_to = \"Sex\", # name for new column containing the category (\"m\" or \"f\") \n values_to = \"Population\") %>% # name for new column containing the numeric values pivoted\n mutate(Sex = recode(Sex, # re-code values for clarity\n \"m\" = \"Male\",\n \"f\" = \"Female\"))\n\nThe combined population data now look like this (click through to see countries A and B):\n\n\n\n\n\n\nAnd now we perform similar operations on the two deaths datasets.\n\ndeaths_countries <- A_deaths %>% # begin with country A deaths dataset\n bind_rows(B_deaths) %>% # bind rows with B dataset, because cols are identically named\n pivot_longer( # pivot longer\n cols = c(Male, Female), # column to transform into one\n names_to = \"Sex\", # name for new column containing the category (\"m\" or \"f\") \n values_to = \"Deaths\") %>% # name for new column containing the numeric values pivoted\n rename(age_cat5 = AgeCat) # rename for clarity\n\nThe deaths data now look like this, and contain data from both countries:\n\n\n\n\n\n\nWe now join the deaths and population data based on common columns Country, age_cat5, and Sex. This adds the column Deaths.\n\ncountry_data <- pop_countries %>% \n left_join(deaths_countries, by = c(\"Country\", \"age_cat5\", \"Sex\"))\n\nWe can now classify Sex, age_cat5, and Country as factors and set the level order using fct_relevel() function from the forcats package, as described in the page on Factors. Note, classifying the factor levels doesn’t visibly change the data, but the arrange() command does sort it by Country, age category, and sex.\n\ncountry_data <- country_data %>% \n mutate(\n Country = fct_relevel(Country, \"A\", \"B\"),\n \n Sex = fct_relevel(Sex, \"Male\", \"Female\"),\n \n age_cat5 = fct_relevel(\n age_cat5,\n \"0-4\", \"5-9\", \"10-14\", \"15-19\",\n \"20-24\", \"25-29\", \"30-34\", \"35-39\",\n \"40-44\", \"45-49\", \"50-54\", \"55-59\",\n \"60-64\", \"65-69\", \"70-74\",\n \"75-79\", \"80-84\", \"85\")) %>% \n \n arrange(Country, age_cat5, Sex)\n\n\n\n\n\n\n\nCAUTION: If you have few deaths per stratum, consider using 10-, or 15-year categories, instead of 5-year categories for age.\n\n\nLoad reference population\nLastly, for the direct standardisation, we import the reference population (world “standard population” by sex):\n\n# Reference population\nstandard_pop_data <- import(\"world_standard_population_by_sex.csv\")\n\n\n\n\n\n\n\n\n\n\nClean reference population\nThe age category values in the country_data and standard_pop_data data frames will need to be aligned.\nCurrently, the values of the column age_cat5 from the standard_pop_data data frame contain the word “years” and “plus”, while those of the country_data data frame do not. We will have to make the age category values match. We use str_replace_all() from the stringr package, as described in the page on Characters and strings, to replace these patterns with no space \"\".\n\n# Remove specific string from column values\nstandard_pop_clean <- standard_pop_data %>%\n mutate(\n age_cat5 = str_replace_all(age_cat5, \"years\", \"\"), # remove \"year\"\n age_cat5 = str_replace_all(age_cat5, \"plus\", \"\"), # remove \"plus\"\n age_cat5 = str_replace_all(age_cat5, \" \", \"\")) %>% # remove \" \" space\n \n rename(pop = WorldStandardPopulation)\n\nCAUTION: If you try to use str_replace_all() to remove a plus symbol, it won’t work because it is a special symbol. “Escape” the specialnes by putting two back slashes in front, as in str_replace_call(column, \"\\\\+\", \"\"). \n\n\nCreate dataset with standard population\nFinally, the package PHEindicatormethods, detailed below, expects the standard populations joined to the country event and population counts. So, we will create a dataset all_data for that purpose.\n\nall_data <- left_join(country_data, \n standard_pop_clean, \n by = c(\"age_cat5\", \"Sex\"))\n\nThis complete dataset looks like this:", "crumbs": [ "Analysis", "21  Standardised rates" @@ -1890,8 +1890,8 @@ "objectID": "new_pages/standardization.html#standard_phe", "href": "new_pages/standardization.html#standard_phe", "title": "21  Standardised rates", - "section": "21.4 PHEindicatormethods package", - "text": "21.4 PHEindicatormethods package\nAnother way of calculating standardized rates is with the PHEindicatormethods package. This package allows you to calculate directly as well as indirectly standardized rates. We will show both.\nThis section will use the all_data data frame created at the end of the Preparation section. This data frame includes the country populations, death events, and the world standard reference population. You can view it here.\n\n\nDirectly standardized rates\nBelow, we first group the data by Country and then pass it to the function phe_dsr() to get directly standardized rates per country.\nOf note - the reference (standard) population can be provided as a column within the country-specific data frame or as a separate vector. If provided within the country-specific data frame, you have to set stdpoptype = \"field\". If provided as a vector, set stdpoptype = \"vector\". In the latter case, you have to make sure the ordering of rows by strata is similar in both the country-specific data frame and the reference population, as records will be matched by position. In our example below, we provided the reference population as a column within the country-specific data frame.\nSee the help with ?phr_dsr or the links in the References section for more information.\n\n# Calculate rates per country directly standardized for age and sex\nmortality_ds_rate_phe <- all_data %>%\n group_by(Country) %>%\n PHEindicatormethods::phe_dsr(\n x = Deaths, # column with observed number of events\n n = Population, # column with non-standard pops for each stratum\n stdpop = pop, # standard populations for each stratum\n stdpoptype = \"field\") # either \"vector\" for a standalone vector or \"field\" meaning std populations are in the data \n\n# Print table\nknitr::kable(mortality_ds_rate_phe)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCountry\ntotal_count\ntotal_pop\nvalue\nlowercl\nuppercl\nconfidence\nstatistic\nmethod\n\n\n\n\nA\n11344\n86790567\n23.56686\n23.08107\n24.05944\n95%\ndsr per 100000\nDobson\n\n\nB\n9955\n52898281\n19.32549\n18.45516\n20.20882\n95%\ndsr per 100000\nDobson\n\n\n\n\n\n\n\n\nIndirectly standardized rates\nFor indirect standardization, you need a reference population with the number of deaths and number of population per stratum. In this example, we will be calculating rates for country A using country B as the reference population, as the standard_pop_clean reference population does not include number of deaths per stratum.\nBelow, we first create the reference population from country B. Then, we pass mortality and population data for country A, combine it with the reference population, and pass it to the function calculate_ISRate(), to get indirectly standardized rates. Of course, you can do it also vice versa.\nOf note - in our example below, the reference population is provided as a separate data frame. In this case, we make sure that x =, n =, x_ref = and n_ref = vectors are all ordered by the same standardization category (stratum) values as that in our country-specific data frame, as records will be matched by position.\nSee the help with ?phr_isr or the links in the References section for more information.\n\n# Create reference population\nrefpopCountryB <- country_data %>% \n filter(Country == \"B\") \n\n# Calculate rates for country A indirectly standardized by age and sex\nmortality_is_rate_phe_A <- country_data %>%\n filter(Country == \"A\") %>%\n PHEindicatormethods::calculate_ISRate(\n x = Deaths, # column with observed number of events\n n = Population, # column with non-standard pops for each stratum\n x_ref = refpopCountryB$Deaths, # reference number of deaths for each stratum\n n_ref = refpopCountryB$Population) # reference population for each stratum\n\n# Print table\nknitr::kable(mortality_is_rate_phe_A)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nobserved\nexpected\nref_rate\nvalue\nlowercl\nuppercl\nconfidence\nstatistic\nmethod\n\n\n\n\n11344\n15847.42\n18.81914\n13.47123\n13.22446\n13.72145\n95%\nindirectly standardised rate per 100000\nByars", + "section": "21.3 PHEindicatormethods package", + "text": "21.3 PHEindicatormethods package\nOne way of calculating standardized rates is with the PHEindicatormethods package. This package allows you to calculate directly as well as indirectly standardized rates. We will show both.\nThis section will use the all_data data frame created at the end of the Preparation section. This data frame includes the country populations, death events, and the world standard reference population. You can view it here.\n\n\nDirectly standardized rates\nBelow, we first group the data by Country and then pass it to the function phe_dsr() to get directly standardized rates per country.\nOf note - the reference (standard) population can be provided as a column within the country-specific data frame or as a separate vector. If provided within the country-specific data frame, you have to set stdpoptype = \"field\". If provided as a vector, set stdpoptype = \"vector\". In the latter case, you have to make sure the ordering of rows by strata is similar in both the country-specific data frame and the reference population, as records will be matched by position. In our example below, we provided the reference population as a column within the country-specific data frame.\nSee the help with ?phr_dsr or the links in the References section for more information.\n\n# Calculate rates per country directly standardized for age and sex\nmortality_ds_rate_phe <- all_data %>%\n group_by(Country) %>%\n PHEindicatormethods::phe_dsr(\n x = Deaths, # column with observed number of events\n n = Population, # column with non-standard pops for each stratum\n stdpop = pop, # standard populations for each stratum\n stdpoptype = \"field\") # either \"vector\" for a standalone vector or \"field\" meaning std populations are in the data \n\n# Print table\nknitr::kable(mortality_ds_rate_phe)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCountry\ntotal_count\ntotal_pop\nvalue\nlowercl\nuppercl\nconfidence\nstatistic\nmethod\n\n\n\n\nA\n11344\n86790567\n23.56686\n23.08107\n24.05944\n95%\ndsr per 100000\nDobson\n\n\nB\n9955\n52898281\n19.32549\n18.45516\n20.20882\n95%\ndsr per 100000\nDobson\n\n\n\n\n\n\n\n\nIndirectly standardized rates\nFor indirect standardization, you need a reference population with the number of deaths and number of population per stratum. In this example, we will be calculating rates for country A using country B as the reference population, as the standard_pop_clean reference population does not include number of deaths per stratum.\nBelow, we first create the reference population from country B. Then, we pass mortality and population data for country A, combine it with the reference population, and pass it to the function calculate_ISRate(), to get indirectly standardized rates. Of course, you can do it also vice versa.\nOf note - in our example below, the reference population is provided as a separate data frame. In this case, we make sure that x =, n =, x_ref = and n_ref = vectors are all ordered by the same standardization category (stratum) values as that in our country-specific data frame, as records will be matched by position.\nSee the help with ?phr_isr or the links in the References section for more information.\n\n# Create reference population\nrefpopCountryB <- country_data %>% \n filter(Country == \"B\") \n\n# Calculate rates for country A indirectly standardized by age and sex\nmortality_is_rate_phe_A <- country_data %>%\n filter(Country == \"A\") %>%\n PHEindicatormethods::calculate_ISRate(\n x = Deaths, # column with observed number of events\n n = Population, # column with non-standard pops for each stratum\n x_ref = refpopCountryB$Deaths, # reference number of deaths for each stratum\n n_ref = refpopCountryB$Population) # reference population for each stratum\n\n# Print table\nknitr::kable(mortality_is_rate_phe_A)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nobserved\nexpected\nref_rate\nvalue\nlowercl\nuppercl\nconfidence\nstatistic\nmethod\n\n\n\n\n11344\n15847.42\n18.81914\n13.47123\n13.22446\n13.72145\n95%\nindirectly standardised rate per 100000\nByars", "crumbs": [ "Analysis", "21  Standardised rates" @@ -1901,8 +1901,8 @@ "objectID": "new_pages/standardization.html#resources", "href": "new_pages/standardization.html#resources", "title": "21  Standardised rates", - "section": "21.5 Resources", - "text": "21.5 Resources\nIf you would like to see another reproducible example using dsr please see this vignette\nFor another example using PHEindicatormethods, please go to this website\nSee the PHEindicatormethods reference pdf file", + "section": "21.4 Resources", + "text": "21.4 Resources\nFor another example using PHEindicatormethods, please go to this website.\nSee the PHEindicatormethods reference pdf file.", "crumbs": [ "Analysis", "21  Standardised rates" @@ -1935,7 +1935,7 @@ "href": "new_pages/moving_average.html#calculate-with-slider", "title": "22  Moving averages", "section": "22.2 Calculate with slider", - "text": "22.2 Calculate with slider\nUse this approach to calculate a moving average in a data frame prior to plotting.\nThe slider package provides several “sliding window” functions to compute rolling averages, cumulative sums, rolling regressions, etc. It treats a data frame as a vector of rows, allowing iteration row-wise over a data frame.\nHere are some of the common functions:\n\nslide_dbl() - iterates through a numeric (hence “_dbl”) column performing an operation using a sliding window\n\nslide_sum() - rolling sum shortcut function for slide_dbl()\n\nslide_mean() - rolling average shortcut function for slide_dbl()\n\nslide_index_dbl() - applies the rolling window on a numeric column using a separate column to index the window progression (useful if rolling by date with some dates absent)\n\nslide_index_sum() - rolling sum shortcut function with indexing\n\nslide_index_mean() - rolling mean shortcut function with indexing\n\n\nThe slider package has many other functions that are covered in the Resources section of this page. We briefly touch upon the most common.\nCore arguments\n\n.x, the first argument by default, is the vector to iterate over and to apply the function to\n\n.i = for the “index” versions of the slider functions - provide a column to “index” the roll on (see section below)\n\n.f =, the second argument by default, either:\n\nA function, written without parentheses, like mean, or\n\nA formula, which will be converted into a function. For example ~ .x - mean(.x) will return the result of the current value minus the mean of the window’s value\n\nFor more details see this reference material\n\nWindow size\nSpecify the size of the window by using either .before, .after, or both arguments:\n\n.before = - Provide an integer\n\n.after = - Provide an integer\n\n.complete = - Set this to TRUE if you only want calculation performed on complete windows\n\nFor example, to achieve a 7-day window including the current value and the six previous, use .before = 6. To achieve a “centered” window provide the same number to both .before = and .after =.\nBy default, .complete = will be FALSE so if the full window of rows does not exist, the functions will use available rows to perform the calculation. Setting to TRUE restricts so calculations are only performed on complete windows.\nExpanding window\nTo achieve cumulative operations, set the .before = argument to Inf. This will conduct the operation on the current value and all coming before.\n\nRolling by date\nThe most likely use-case of a rolling calculation in applied epidemiology is to examine a metric over time. For example, a rolling measurement of case incidence, based on daily case counts.\nIf you have clean time series data with values for every date, you may be OK to use slide_dbl(), as demonstrated here in the Time series and outbreak detection page.\nHowever, in many applied epidemiology circumstances you may have dates absent from your data, where there are no events recorded. In these cases, it is best to use the “index” versions of the slider functions.\n\n\nIndexed data\nBelow, we show an example using slide_index_dbl() on the case linelist. Let us say that our objective is to calculate a rolling 7-day incidence - the sum of cases using a rolling 7-day window. If you are looking for an example of rolling average, see the section below on grouped rolling.\nTo begin, the dataset daily_counts is created to reflect the daily case counts from the linelist, as calculated with count() from dplyr.\n\n# make dataset of daily counts\ndaily_counts <- linelist %>% \n count(date_hospitalisation, name = \"new_cases\")\n\nHere is the daily_counts data frame - there are nrow(daily_counts) rows, each day is represented by one row, but especially early in the epidemic some days are not present (there were no cases admitted on those days).\n\n\n\n\n\n\nIt is crucial to recognize that a standard rolling function (like slide_dbl() would use a window of 7 rows, not 7 days. So, if there are any absent dates, some windows will actually extend more than 7 calendar days!\nA “smart” rolling window can be achieved with slide_index_dbl(). The “index” means that the function uses a separate column as an “index” for the rolling window. The window is not simply based on the rows of the data frame.\nIf the index column is a date, you have the added ability to specify the window extent to .before = and/or .after = in units of lubridate days() or months(). If you do these things, the function will include absent days in the windows as if they were there (as NA values).\nLet’s show a comparison. Below, we calculate rolling 7-day case incidence with regular and indexed windows.\n\nrolling <- daily_counts %>% \n mutate( # create new columns\n # Using slide_dbl()\n ###################\n reg_7day = slide_dbl(\n new_cases, # calculate on new_cases\n .f = ~sum(.x, na.rm = T), # function is sum() with missing values removed\n .before = 6), # window is the ROW and 6 prior ROWS\n \n # Using slide_index_dbl()\n #########################\n indexed_7day = slide_index_dbl(\n new_cases, # calculate on new_cases\n .i = date_hospitalisation, # indexed with date_onset \n .f = ~sum(.x, na.rm = TRUE), # function is sum() with missing values removed\n .before = days(6)) # window is the DAY and 6 prior DAYS\n )\n\nObserve how in the regular column for the first 7 rows the count steadily increases despite the rows not being within 7 days of each other! The adjacent “indexed” column accounts for these absent calendar days, so its 7-day sums are much lower, at least in this period of the epidemic when the cases a farther between.\n\n\n\n\n\n\nNow you can plot these data using ggplot():\n\nggplot(data = rolling)+\n geom_line(mapping = aes(x = date_hospitalisation, y = indexed_7day), size = 1)\n\nWarning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.\nℹ Please use `linewidth` instead.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nRolling by group\nIf you group your data prior to using a slider function, the sliding windows will be applied by group. Be careful to arrange your rows in the desired order by group.\nEach time a new group begins, the sliding window will re-start. Therefore, one nuance to be aware of is that if your data are grouped and you have set .complete = TRUE, you will have empty values at each transition between groups. As the function moved downward through the rows, every transition in the grouping column will re-start the accrual of the minimum window size to allow a calculation.\nSee handbook page on Grouping data for details on grouping data.\nBelow, we count linelist cases by date and by hospital. Then we arrange the rows in ascending order, first ordering by hospital and then within that by date. Next we set group_by(). Then we can create our new rolling average.\n\ngrouped_roll <- linelist %>%\n\n count(hospital, date_hospitalisation, name = \"new_cases\") %>% \n\n arrange(hospital, date_hospitalisation) %>% # arrange rows by hospital and then by date\n \n group_by(hospital) %>% # group by hospital \n \n mutate( # rolling average \n mean_7day_hosp = slide_index_dbl(\n .x = new_cases, # the count of cases per hospital-day\n .i = date_hospitalisation, # index on date of admission\n .f = mean, # use mean() \n .before = days(6) # use the day and the 6 days prior\n )\n )\n\nHere is the new dataset:\n\n\n\n\n\n\nWe can now plot the moving averages, displaying the data by group by specifying ~ hospital to facet_wrap() in ggplot(). For fun, we plot two geometries - a geom_col() showing the daily case counts and a geom_line() showing the 7-day moving average.\n\nggplot(data = grouped_roll)+\n geom_col( # plot daly case counts as grey bars\n mapping = aes(\n x = date_hospitalisation,\n y = new_cases),\n fill = \"grey\",\n width = 1)+\n geom_line( # plot rolling average as line colored by hospital\n mapping = aes(\n x = date_hospitalisation,\n y = mean_7day_hosp,\n color = hospital),\n size = 1)+\n facet_wrap(~hospital, ncol = 2)+ # create mini-plots per hospital\n theme_classic()+ # simplify background \n theme(legend.position = \"none\")+ # remove legend\n labs( # add plot labels\n title = \"7-day rolling average of daily case incidence\",\n x = \"Date of admission\",\n y = \"Case incidence\")\n\n\n\n\n\n\n\n\nDANGER: If you get an error saying “slide() was deprecated in tsibble 0.9.0 and is now defunct. Please use slider::slide() instead.”, it means that the slide() function from the tsibble package is masking the slide() function from slider package. Fix this by specifying the package in the command, such as slider::slide_dbl().", + "text": "22.2 Calculate with slider\nUse this approach to calculate a moving average in a data frame prior to plotting.\nThe slider package provides several “sliding window” functions to compute rolling averages, cumulative sums, rolling regressions, etc. It treats a data frame as a vector of rows, allowing iteration row-wise over a data frame.\nHere are some of the common functions:\n\nslide_dbl() - iterates through a numeric (hence “_dbl”) column performing an operation using a sliding window\n\nslide_sum() - rolling sum shortcut function for slide_dbl()\n\nslide_mean() - rolling average shortcut function for slide_dbl()\n\nslide_index_dbl() - applies the rolling window on a numeric column using a separate column to index the window progression (useful if rolling by date with some dates absent)\n\nslide_index_sum() - rolling sum shortcut function with indexing\n\nslide_index_mean() - rolling mean shortcut function with indexing\n\n\nThe slider package has many other functions that are covered in the Resources section of this page. We briefly touch upon the most common.\nCore arguments\n\nx, the first argument by default, is the vector to iterate over and to apply the function to\n\ni = for the “index” versions of the slider functions - provide a column to “index” the roll on (see section below)\n\nf =, the second argument by default, either:\n\nA function, written without parentheses, like mean, or,\nA formula, which will be converted into a function For example ~ x - mean(x) will return the result of the current value minus the mean of the window’s value\n\nFor more details see this reference material\n\nWindow size\nSpecify the size of the window by using either .before, .after, or both arguments:\n\nbefore = - Provide an integer\n\nafter = - Provide an integer\n\ncomplete = - Set this to TRUE if you only want calculation performed on complete windows\n\nFor example, to achieve a 7-day window including the current value and the six previous, use .before = 6. To achieve a “centered” window provide the same number to both .before = and .after =.\nBy default, .complete = will be FALSE so if the full window of rows does not exist, the functions will use available rows to perform the calculation. Setting to TRUE restricts so calculations are only performed on complete windows.\nExpanding window\nTo achieve cumulative operations, set the .before = argument to Inf. This will conduct the operation on the current value and all coming before.\n\nRolling by date\nThe most likely use-case of a rolling calculation in applied epidemiology is to examine a metric over time. For example, a rolling measurement of case incidence, based on daily case counts.\nIf you have clean time series data with values for every date, you may be OK to use slide_dbl(), as demonstrated here in the Time series and outbreak detection page.\nHowever, in many applied epidemiology circumstances you may have dates absent from your data, where there are no events recorded. In these cases, it is best to use the “index” versions of the slider functions.\n\n\nIndexed data\nBelow, we show an example using slide_index_dbl() on the case linelist. Let us say that our objective is to calculate a rolling 7-day incidence - the sum of cases using a rolling 7-day window. If you are looking for an example of rolling average, see the section below on grouped rolling.\nTo begin, the dataset daily_counts is created to reflect the daily case counts from the linelist, as calculated with count() from dplyr.\n\n# make dataset of daily counts\ndaily_counts <- linelist %>% \n count(date_hospitalisation, name = \"new_cases\")\n\nHere is the daily_counts data frame - there are nrow(daily_counts) rows, each day is represented by one row, but especially early in the epidemic some days are not present (there were no cases admitted on those days).\n\n\n\n\n\n\nIt is crucial to recognize that a standard rolling function (like slide_dbl() would use a window of 7 rows, not 7 days. So, if there are any absent dates, some windows will actually extend more than 7 calendar days!\nA “smart” rolling window can be achieved with slide_index_dbl(). The “index” means that the function uses a separate column as an “index” for the rolling window. The window is not simply based on the rows of the data frame.\nIf the index column is a date, you have the added ability to specify the window extent to .before = and/or .after = in units of lubridate days() or months(). If you do these things, the function will include absent days in the windows as if they were there (as NA values).\nLet’s show a comparison. Below, we calculate rolling 7-day case incidence with regular and indexed windows.\n\nrolling <- daily_counts %>% \n mutate( # create new columns\n # Using slide_dbl()\n ###################\n reg_7day = slide_dbl(\n new_cases, # calculate on new_cases\n .f = ~sum(.x, na.rm = T), # function is sum() with missing values removed\n .before = 6), # window is the ROW and 6 prior ROWS\n \n # Using slide_index_dbl()\n #########################\n indexed_7day = slide_index_dbl(\n new_cases, # calculate on new_cases\n .i = date_hospitalisation, # indexed with date_onset \n .f = ~sum(.x, na.rm = TRUE), # function is sum() with missing values removed\n .before = days(6)) # window is the DAY and 6 prior DAYS\n )\n\nObserve how in the regular column for the first 7 rows the count steadily increases despite the rows not being within 7 days of each other! The adjacent “indexed” column accounts for these absent calendar days, so its 7-day sums are much lower, at least in this period of the epidemic when the cases a farther between.\n\n\n\n\n\n\nNow you can plot these data using ggplot():\n\nggplot(data = rolling) +\n geom_line(mapping = aes(x = date_hospitalisation, y = indexed_7day), linewidth = 1)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nRolling by group\nIf you group your data prior to using a slider function, the sliding windows will be applied by group. Be careful to arrange your rows in the desired order by group.\nEach time a new group begins, the sliding window will re-start. Therefore, one nuance to be aware of is that if your data are grouped and you have set .complete = TRUE, you will have empty values at each transition between groups. As the function moved downward through the rows, every transition in the grouping column will re-start the accrual of the minimum window size to allow a calculation.\nSee handbook page on Grouping data for details on grouping data.\nBelow, we count linelist cases by date and by hospital. Then we arrange the rows in ascending order, first ordering by hospital and then within that by date. Next we set group_by(). Then we can create our new rolling average.\n\ngrouped_roll <- linelist %>%\n\n count(hospital, date_hospitalisation, name = \"new_cases\") %>% \n\n arrange(hospital, date_hospitalisation) %>% # arrange rows by hospital and then by date\n \n group_by(hospital) %>% # group by hospital \n \n mutate( # rolling average \n mean_7day_hosp = slide_index_dbl(\n .x = new_cases, # the count of cases per hospital-day\n .i = date_hospitalisation, # index on date of admission\n .f = mean, # use mean() \n .before = days(6) # use the day and the 6 days prior\n )\n )\n\nHere is the new dataset:\n\n\n\n\n\n\nWe can now plot the moving averages, displaying the data by group by specifying ~ hospital to facet_wrap() in ggplot(). For fun, we plot two geometries - a geom_col() showing the daily case counts and a geom_line() showing the 7-day moving average.\n\nggplot(data = grouped_roll) +\n geom_col( # plot daly case counts as grey bars\n mapping = aes(\n x = date_hospitalisation,\n y = new_cases),\n fill = \"grey\",\n width = 1) +\n geom_line( # plot rolling average as line colored by hospital\n mapping = aes(\n x = date_hospitalisation,\n y = mean_7day_hosp,\n color = hospital),\n size = 1) +\n facet_wrap(~hospital, ncol = 2) + # create mini-plots per hospital\n theme_classic() + # simplify background \n theme(legend.position = \"none\") + # remove legend\n labs( # add plot labels\n title = \"7-day rolling average of daily case incidence\",\n x = \"Date of admission\",\n y = \"Case incidence\")\n\n\n\n\n\n\n\n\nDANGER: If you get an error saying “slide() was deprecated in tsibble 0.9.0 and is now defunct. Please use slider::slide() instead.”, it means that the slide() function from the tsibble package is masking the slide() function from slider package. Fix this by specifying the package in the command, such as slider::slide_dbl().", "crumbs": [ "Analysis", "22  Moving averages" @@ -1946,7 +1946,7 @@ "href": "new_pages/moving_average.html#calculate-with-tidyquant-within-ggplot", "title": "22  Moving averages", "section": "22.3 Calculate with tidyquant within ggplot()", - "text": "22.3 Calculate with tidyquant within ggplot()\nThe package tidyquant offers another approach to calculating moving averages - this time from within a ggplot() command itself.\nBelow the linelist data are counted by date of onset, and this is plotted as a faded line (alpha < 1). Overlaid on top is a line created with geom_ma() from the package tidyquant, with a set window of 7 days (n = 7) with specified color and thickness.\nBy default geom_ma() uses a simple moving average (ma_fun = \"SMA\"), but other types can be specified, such as:\n\n“EMA” - exponential moving average (more weight to recent observations)\n\n“WMA” - weighted moving average (wts are used to weight observations in the moving average)\n\nOthers can be found in the function documentation\n\n\nlinelist %>% \n count(date_onset) %>% # count cases per day\n drop_na(date_onset) %>% # remove cases missing onset date\n ggplot(aes(x = date_onset, y = n))+ # start ggplot\n geom_line( # plot raw values\n size = 1,\n alpha = 0.2 # semi-transparent line\n )+ \n tidyquant::geom_ma( # plot moving average\n n = 7, \n size = 1,\n color = \"blue\")+ \n theme_minimal() # simple background\n\nWarning: Using the `size` aesthetic in this geom was deprecated in ggplot2 3.4.0.\nℹ Please use `linewidth` in the `default_aes` field and elsewhere instead.\n\n\n\n\n\n\n\n\n\nSee this vignette for more details on the options available within tidyquant.", + "text": "22.3 Calculate with tidyquant within ggplot()\nThe package tidyquant offers another approach to calculating moving averages - this time from within a ggplot() command itself.\nBelow the linelist data are counted by date of onset, and this is plotted as a faded line (alpha < 1). Overlaid on top is a line created with geom_ma() from the package tidyquant, with a set window of 7 days (n = 7) with specified color and thickness.\nBy default geom_ma() uses a simple moving average (ma_fun = \"SMA\"), but other types can be specified, such as:\n\n“EMA” - exponential moving average (more weight to recent observations)\n“WMA” - weighted moving average (wts are used to weight observations in the moving average)\nOthers can be found in the function documentation\n\n\nlinelist %>% \n count(date_onset) %>% # count cases per day\n drop_na(date_onset) %>% # remove cases missing onset date\n ggplot(aes(x = date_onset, y = n)) + # start ggplot\n geom_line( # plot raw values\n size = 1,\n alpha = 0.2 # semi-transparent line\n ) + \n tidyquant::geom_ma( # plot moving average\n n = 7, \n size = 1,\n color = \"blue\") + \n theme_minimal() # simple background\n\n\n\n\n\n\n\n\nSee this vignette for more details on the options available within tidyquant.", "crumbs": [ "Analysis", "22  Moving averages" @@ -1979,7 +1979,7 @@ "href": "new_pages/time_series.html#overview", "title": "23  Time series and outbreak detection", "section": "", - "text": "Time series data\nDescriptive analysis\nFitting regressions\nRelation of two time series\nOutbreak detection\nInterrupted time series", + "text": "Time series data.\nDescriptive analysis.\nFitting regressions.\nRelation of two time series.\nOutbreak detection.\nInterrupted time series.", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -1990,7 +1990,7 @@ "href": "new_pages/time_series.html#preparation", "title": "23  Time series and outbreak detection", "section": "23.2 Preparation", - "text": "23.2 Preparation\n\nPackages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(rio, # File import\n here, # File locator\n tsibble, # handle time series datasets\n slider, # for calculating moving averages\n imputeTS, # for filling in missing values\n feasts, # for time series decomposition and autocorrelation\n forecast, # fit sin and cosin terms to data (note: must load after feasts)\n trending, # fit and assess models \n tmaptools, # for getting geocoordinates (lon/lat) based on place names\n ecmwfr, # for interacting with copernicus sateliate CDS API\n stars, # for reading in .nc (climate data) files\n units, # for defining units of measurement (climate data)\n yardstick, # for looking at model accuracy\n surveillance, # for aberration detection\n tidyverse # data management + ggplot2 graphics\n )\n\n\n\nLoad data\nYou can download all the data used in this handbook via the instructions in the Download handbook and data page.\nThe example dataset used in this section is weekly counts of campylobacter cases reported in Germany between 2001 and 2011. You can click here to download this data file (.xlsx).\nThis dataset is a reduced version of the dataset available in the surveillance package. (for details load the surveillance package and see ?campyDE)\nImport these data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import the counts into R\ncounts <- rio::import(\"campylobacter_germany.xlsx\")\n\nThe first 10 rows of the counts are displayed below.\n\n\n\n\n\n\n\n\nClean data\nThe code below makes sure that the date column is in the appropriate format. For this tab we will be using the tsibble package and so the yearweek function will be used to create a calendar week variable. There are several other ways of doing this (see the Working with dates page for details), however for time series its best to keep within one framework (tsibble).\n\n## ensure the date column is in the appropriate format\ncounts$date <- as.Date(counts$date)\n\n## create a calendar week variable \n## fitting ISO definitons of weeks starting on a monday\ncounts <- counts %>% \n mutate(epiweek = yearweek(date, week_start = 1))\n\n\n\nDownload climate data\nIn the relation of two time series section of this page, we will be comparing campylobacter case counts to climate data.\nClimate data for anywhere in the world can be downloaded from the EU’s Copernicus Satellite. These are not exact measurements, but based on a model (similar to interpolation), however the benefit is global hourly coverage as well as forecasts.\nYou can download each of these climate data files from the Download handbook and data page.\nFor purposes of demonstration here, we will show R code to use the ecmwfr package to pull these data from the Copernicus climate data store. You will need to create a free account in order for this to work. The package website has a useful walkthrough of how to do this. Below is example code of how to go about doing this, once you have the appropriate API keys. You have to replace the X’s below with your account IDs. You will need to download one year of data at a time otherwise the server times-out.\nIf you are not sure of the coordinates for a location you want to download data for, you can use the tmaptools package to pull the coordinates off open street maps. An alternative option is the photon package, however this has not been released on to CRAN yet; the nice thing about photon is that it provides more contextual data for when there are several matches for your search.\n\n## retrieve location coordinates\ncoords <- geocode_OSM(\"Germany\", geometry = \"point\")\n\n## pull together long/lats in format for ERA-5 querying (bounding box) \n## (as just want a single point can repeat coords)\nrequest_coords <- str_glue_data(coords$coords, \"{y}/{x}/{y}/{x}\")\n\n\n## Pulling data modelled from copernicus satellite (ERA-5 reanalysis)\n## https://cds.climate.copernicus.eu/cdsapp#!/software/app-era5-explorer?tab=app\n## https://github.com/bluegreen-labs/ecmwfr\n\n## set up key for weather data \nwf_set_key(user = \"XXXXX\",\n key = \"XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX\",\n service = \"cds\") \n\n## run for each year of interest (otherwise server times out)\nfor (i in 2002:2011) {\n \n ## pull together a query \n ## see here for how to do: https://bluegreen-labs.github.io/ecmwfr/articles/cds_vignette.html#the-request-syntax\n ## change request to a list using addin button above (python to list)\n ## Target is the name of the output file!!\n request <- request <- list(\n product_type = \"reanalysis\",\n format = \"netcdf\",\n variable = c(\"2m_temperature\", \"total_precipitation\"),\n year = c(i),\n month = c(\"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\"),\n day = c(\"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\",\n \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\",\n \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\"),\n time = c(\"00:00\", \"01:00\", \"02:00\", \"03:00\", \"04:00\", \"05:00\", \"06:00\", \"07:00\",\n \"08:00\", \"09:00\", \"10:00\", \"11:00\", \"12:00\", \"13:00\", \"14:00\", \"15:00\",\n \"16:00\", \"17:00\", \"18:00\", \"19:00\", \"20:00\", \"21:00\", \"22:00\", \"23:00\"),\n area = request_coords,\n dataset_short_name = \"reanalysis-era5-single-levels\",\n target = paste0(\"germany_weather\", i, \".nc\")\n )\n \n ## download the file and store it in the current working directory\n file <- wf_request(user = \"XXXXX\", # user ID (for authentication)\n request = request, # the request\n transfer = TRUE, # download the file\n path = here::here(\"data\", \"Weather\")) ## path to save the data\n }\n\n\n\nLoad climate data\nWhether you downloaded the climate data via our handbook, or used the code above, you now should have 10 years of “.nc” climate data files stored in the same folder on your computer.\nUse the code below to import these files into R with the stars package.\n\n## define path to weather folder \nfile_paths <- list.files(\n here::here(\"data\", \"time_series\", \"weather\"), # replace with your own file path \n full.names = TRUE)\n\n## only keep those with the current name of interest \nfile_paths <- file_paths[str_detect(file_paths, \"germany\")]\n\n## read in all the files as a stars object \ndata <- stars::read_stars(file_paths)\n\nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \nt2m, tp, \n\n\nOnce these files have been imported as the object data, we will convert them to a data frame.\n\n## change to a data frame \ntemp_data <- as_tibble(data) %>% \n ## add in variables and correct units\n mutate(\n ## create an calendar week variable \n epiweek = tsibble::yearweek(time), \n ## create a date variable (start of calendar week)\n date = as.Date(epiweek),\n ## change temperature from kelvin to celsius\n t2m = set_units(t2m, celsius), \n ## change precipitation from metres to millimetres \n tp = set_units(tp, mm)) %>% \n ## group by week (keep the date too though)\n group_by(epiweek, date) %>% \n ## get the average per week\n summarise(t2m = as.numeric(mean(t2m)), \n tp = as.numeric(mean(tp)))\n\n`summarise()` has grouped output by 'epiweek'. You can override using the\n`.groups` argument.", + "text": "23.2 Preparation\n\nPackages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n tsibble, # handle time series datasets\n slider, # for calculating moving averages\n imputeTS, # for filling in missing values\n feasts, # for time series decomposition and autocorrelation\n forecast, # fit sin and cosin terms to data (note: must load after feasts)\n trending, # fit and assess models \n tmaptools, # for getting geocoordinates (lon/lat) based on place names\n ecmwfr, # for interacting with copernicus sateliate CDS API\n stars, # for reading in .nc (climate data) files\n units, # for defining units of measurement (climate data)\n yardstick, # for looking at model accuracy\n surveillance, # for aberration detection\n tidyverse # data management + ggplot2 graphics\n )\n\n\n\nLoad data\nYou can download all the data used in this handbook via the instructions in the Download handbook and data page.\nThe example dataset used in this section is weekly counts of campylobacter cases reported in Germany between 2001 and 2011. You can click here to download this data file (.xlsx).\nThis dataset is a reduced version of the dataset available in the surveillance package. (for details load the surveillance package and see ?campyDE)\nImport these data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import the counts into R\ncounts <- rio::import(\"campylobacter_germany.xlsx\")\n\nThe first 10 rows of the counts are displayed below.\n\n\n\n\n\n\n\n\nClean data\nThe code below makes sure that the date column is in the appropriate format. For this tab we will be using the tsibble package and so the yearweek function will be used to create a calendar week variable. There are several other ways of doing this (see the Working with dates page for details), however for time series its best to keep within one framework (tsibble).\n\n## ensure the date column is in the appropriate format\ncounts$date <- as.Date(counts$date)\n\n## create a calendar week variable \n## fitting ISO definitons of weeks starting on a monday\ncounts <- counts %>% \n mutate(epiweek = yearweek(date, week_start = 1))\n\n\n\nDownload climate data\nIn the relation of two time series section of this page, we will be comparing campylobacter case counts to climate data.\nClimate data for anywhere in the world can be downloaded from the EU’s Copernicus Satellite. These are not exact measurements, but based on a model (similar to interpolation), however the benefit is global hourly coverage as well as forecasts.\nYou can download each of these climate data files from the Download handbook and data page.\nFor purposes of demonstration here, we will show R code to use the ecmwfr package to pull these data from the Copernicus climate data store. You will need to create a free account in order for this to work. The package website has a useful walkthrough of how to do this. Below is example code of how to go about doing this, once you have the appropriate API keys. You have to replace the X’s below with your account IDs. You will need to download one year of data at a time otherwise the server times-out.\nIf you are not sure of the coordinates for a location you want to download data for, you can use the tmaptools package to pull the coordinates off open street maps. An alternative option is the photon package, however this has not been released on to CRAN yet; the nice thing about photon is that it provides more contextual data for when there are several matches for your search.\n\n## retrieve location coordinates\ncoords <- geocode_OSM(\"Germany\", geometry = \"point\")\n\n## pull together long/lats in format for ERA-5 querying (bounding box) \n## (as just want a single point can repeat coords)\nrequest_coords <- str_glue_data(coords$coords, \"{y}/{x}/{y}/{x}\")\n\n\n## Pulling data modelled from copernicus satellite (ERA-5 reanalysis)\n## https://cds-beta.climate.copernicus.eu/datasets/reanalysis-era5-land?tab=overview\n## https://github.com/bluegreen-labs/ecmwfr\n\n## set up key for weather data \nwf_set_key(key = \"XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX\") \n\n## run for each year of interest (otherwise server times out)\nfor (i in 2002:2011) {\n \n ## pull together a query \n ## see here for how to do: https://bluegreen-labs.github.io/ecmwfr/articles/cds_vignette.html#the-request-syntax\n ## change request to a list using addin button above (python to list)\n ## Target is the name of the output file!!\n request <- request <- list(\n product_type = \"reanalysis\",\n format = \"netcdf\",\n variable = c(\"2m_temperature\", \"total_precipitation\"),\n year = c(i),\n month = c(\"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\"),\n day = c(\"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\",\n \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\",\n \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\"),\n time = c(\"00:00\", \"01:00\", \"02:00\", \"03:00\", \"04:00\", \"05:00\", \"06:00\", \"07:00\",\n \"08:00\", \"09:00\", \"10:00\", \"11:00\", \"12:00\", \"13:00\", \"14:00\", \"15:00\",\n \"16:00\", \"17:00\", \"18:00\", \"19:00\", \"20:00\", \"21:00\", \"22:00\", \"23:00\"),\n area = request_coords,\n dataset_short_name = \"reanalysis-era5-single-levels\",\n target = paste0(\"germany_weather\", i, \".nc\")\n )\n \n ## download the file and store it in the current working directory\n file <- wf_request(user = \"XXXXX\", # user ID (for authentication)\n request = request, # the request\n transfer = TRUE, # download the file\n path = here::here(\"data\", \"Weather\")) ## path to save the data\n }\n\n\n\nLoad climate data\nWhether you downloaded the climate data via our handbook, or used the code above, you now should have 10 years of “.nc” climate data files stored in the same folder on your computer.\nUse the code below to import these files into R with the stars package.\n\n## define path to weather folder \nfile_paths <- list.files(\n here::here(\"data\", \"time_series\", \"weather\"), # replace with your own file path \n full.names = TRUE)\n\n## only keep those with the current name of interest \nfile_paths <- file_paths[str_detect(file_paths, \"germany\")]\n\n## read in all the files as a stars object \ndata <- stars::read_stars(file_paths, quiet = TRUE)\n\nOnce these files have been imported as the object data, we will convert them to a data frame.\n\n## change to a data frame \ntemp_data <- as_tibble(data) %>% \n ## add in variables and correct units\n mutate(\n ## create an calendar week variable \n epiweek = tsibble::yearweek(time), \n ## create a date variable (start of calendar week)\n date = as.Date(epiweek),\n ## change temperature from kelvin to celsius\n t2m = set_units(t2m, celsius), \n ## change precipitation from metres to millimetres \n tp = set_units(tp, mm)) %>% \n ## group by week (keep the date too though)\n group_by(epiweek, date) %>% \n ## get the average per week\n summarise(t2m = as.numeric(mean(t2m)), \n tp = as.numeric(mean(tp)))", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -2012,7 +2012,7 @@ "href": "new_pages/time_series.html#descriptive-analysis", "title": "23  Time series and outbreak detection", "section": "23.4 Descriptive analysis", - "text": "23.4 Descriptive analysis\n\n\nMoving averages\nIf data is very noisy (counts jumping up and down) then it can be helpful to calculate a moving average. In the example below, for each week we calculate the average number of cases from the four previous weeks. This smooths the data, to make it more interpretable. In our case this does not really add much, so we willstick to the interpolated data for further analysis. See the Moving averages page for more detail.\n\n## create a moving average variable (deals with missings)\ncounts <- counts %>% \n ## create the ma_4w variable \n ## slide over each row of the case variable\n mutate(ma_4wk = slider::slide_dbl(case, \n ## for each row calculate the name\n ~ mean(.x, na.rm = TRUE),\n ## use the four previous weeks\n .before = 4))\n\n## make a quick visualisation of the difference \nggplot(counts, aes(x = epiweek)) + \n geom_line(aes(y = case)) + \n geom_line(aes(y = ma_4wk), colour = \"red\")\n\n\n\n\n\n\n\n\n\n\n\nPeriodicity\nBelow we define a custom function to create a periodogram. See the Writing functions page for information about how to write functions in R.\nFirst, the function is defined. Its arguments include a dataset with a column counts, start_week = which is the first week of the dataset, a number to indicate how many periods per year (e.g. 52, 12), and lastly the output style (see details in the code below).\n\n## Function arguments\n#####################\n## x is a dataset\n## counts is variable with count data or rates within x \n## start_week is the first week in your dataset\n## period is how many units in a year \n## output is whether you want return spectral periodogram or the peak weeks\n ## \"periodogram\" or \"weeks\"\n\n# Define function\nperiodogram <- function(x, \n counts, \n start_week = c(2002, 1), \n period = 52, \n output = \"weeks\") {\n \n\n ## make sure is not a tsibble, filter to project and only keep columns of interest\n prepare_data <- dplyr::as_tibble(x)\n \n # prepare_data <- prepare_data[prepare_data[[strata]] == j, ]\n prepare_data <- dplyr::select(prepare_data, {{counts}})\n \n ## create an intermediate \"zoo\" time series to be able to use with spec.pgram\n zoo_cases <- zoo::zooreg(prepare_data, \n start = start_week, frequency = period)\n \n ## get a spectral periodogram not using fast fourier transform \n periodo <- spec.pgram(zoo_cases, fast = FALSE, plot = FALSE)\n \n ## return the peak weeks \n periodo_weeks <- 1 / periodo$freq[order(-periodo$spec)] * period\n \n if (output == \"weeks\") {\n periodo_weeks\n } else {\n periodo\n }\n \n}\n\n## get spectral periodogram for extracting weeks with the highest frequencies \n## (checking of seasonality) \nperiodo <- periodogram(counts, \n case_int, \n start_week = c(2002, 1),\n output = \"periodogram\")\n\n## pull spectrum and frequence in to a dataframe for plotting\nperiodo <- data.frame(periodo$freq, periodo$spec)\n\n## plot a periodogram showing the most frequently occuring periodicity \nggplot(data = periodo, \n aes(x = 1/(periodo.freq/52), y = log(periodo.spec))) + \n geom_line() + \n labs(x = \"Period (Weeks)\", y = \"Log(density)\")\n\n\n\n\n\n\n\n## get a vector weeks in ascending order \npeak_weeks <- periodogram(counts, \n case_int, \n start_week = c(2002, 1), \n output = \"weeks\")\n\nNOTE: It is possible to use the above weeks to add them to sin and cosine terms, however we will use a function to generate these terms (see regression section below) \n\n\n\nDecomposition\nClassical decomposition is used to break a time series down several parts, which when taken together make up for the pattern you see. These different parts are:\n\nThe trend-cycle (the long-term direction of the data)\n\nThe seasonality (repeating patterns)\n\nThe random (what is left after removing trend and season)\n\n\n## decompose the counts dataset \ncounts %>% \n # using an additive classical decomposition model\n model(classical_decomposition(case_int, type = \"additive\")) %>% \n ## extract the important information from the model\n components() %>% \n ## generate a plot \n autoplot()\n\n\n\n\n\n\n\n\n\n\n\nAutocorrelation\nAutocorrelation tells you about the relation between the counts of each week and the weeks before it (called lags).\nUsing the ACF() function, we can produce a plot which shows us a number of lines for the relation at different lags. Where the lag is 0 (x = 0), this line would always be 1 as it shows the relation between an observation and itself (not shown here). The first line shown here (x = 1) shows the relation between each observation and the observation before it (lag of 1), the second shows the relation between each observation and the observation before last (lag of 2) and so on until lag of 52 which shows the relation between each observation and the observation from 1 year (52 weeks before).\nUsing the PACF() function (for partial autocorrelation) shows the same type of relation but adjusted for all other weeks between. This is less informative for determining periodicity.\n\n## using the counts dataset\ncounts %>% \n ## calculate autocorrelation using a full years worth of lags\n ACF(case_int, lag_max = 52) %>% \n ## show a plot\n autoplot()\n\n\n\n\n\n\n\n## using the counts data set \ncounts %>% \n ## calculate the partial autocorrelation using a full years worth of lags\n PACF(case_int, lag_max = 52) %>% \n ## show a plot\n autoplot()\n\n\n\n\n\n\n\n\nYou can formally test the null hypothesis of independence in a time series (i.e.  that it is not autocorrelated) using the Ljung-Box test (in the stats package). A significant p-value suggests that there is autocorrelation in the data.\n\n## test for independance \nBox.test(counts$case_int, type = \"Ljung-Box\")\n\n\n Box-Ljung test\n\ndata: counts$case_int\nX-squared = 462.65, df = 1, p-value < 2.2e-16", + "text": "23.4 Descriptive analysis\n\n\nMoving averages\nIf data is very noisy (counts jumping up and down) then it can be helpful to calculate a moving average. In the example below, for each week we calculate the average number of cases from the four previous weeks. This smooths the data, to make it more interpretable. In our case this does not really add much, so we willstick to the interpolated data for further analysis. See the Moving averages page for more detail.\n\n## create a moving average variable (deals with missings)\ncounts <- counts %>% \n ## create the ma_4w variable \n ## slide over each row of the case variable\n mutate(ma_4wk = slider::slide_dbl(case, \n ## for each row calculate the name\n ~ mean(.x, na.rm = TRUE),\n ## use the four previous weeks\n .before = 4))\n\n## make a quick visualisation of the difference \nggplot(counts, aes(x = epiweek)) + \n geom_line(aes(y = case)) + \n geom_line(aes(y = ma_4wk), colour = \"red\")\n\n\n\n\n\n\n\n\n\n\n\nPeriodicity\nBelow we define a custom function to create a periodogram. See the Writing functions page for information about how to write functions in R.\nFirst, the function is defined. Its arguments include a dataset with a column counts, start_week = which is the first week of the dataset, a number to indicate how many periods per year (e.g. 52, 12), and lastly the output style (see details in the code below).\n\n## Function arguments\n#####################\n## x is a dataset\n## counts is variable with count data or rates within x \n## start_week is the first week in your dataset\n## period is how many units in a year \n## output is whether you want return spectral periodogram or the peak weeks\n ## \"periodogram\" or \"weeks\"\n\n# Define function\nperiodogram <- function(x, \n counts, \n start_week = c(2002, 1), \n period = 52, \n output = \"weeks\") {\n \n\n ## make sure is not a tsibble, filter to project and only keep columns of interest\n prepare_data <- dplyr::as_tibble(x)\n \n # prepare_data <- prepare_data[prepare_data[[strata]] == j, ]\n prepare_data <- dplyr::select(prepare_data, {{counts}})\n \n ## create an intermediate \"zoo\" time series to be able to use with spec.pgram\n zoo_cases <- zoo::zooreg(prepare_data, \n start = start_week, frequency = period)\n \n ## get a spectral periodogram not using fast fourier transform \n periodo <- spec.pgram(zoo_cases, fast = FALSE, plot = FALSE)\n \n ## return the peak weeks \n periodo_weeks <- 1 / periodo$freq[order(-periodo$spec)] * period\n \n if (output == \"weeks\") {\n periodo_weeks\n } else {\n periodo\n }\n \n}\n\n## get spectral periodogram for extracting weeks with the highest frequencies \n## (checking of seasonality) \nperiodo <- periodogram(counts, \n case_int, \n start_week = c(2002, 1),\n output = \"periodogram\")\n\n## pull spectrum and frequence in to a dataframe for plotting\nperiodo <- data.frame(periodo$freq, periodo$spec)\n\n## plot a periodogram showing the most frequently occuring periodicity \nggplot(data = periodo, \n aes(x = 1/(periodo.freq/52), y = log(periodo.spec))) + \n geom_line() + \n labs(x = \"Period (Weeks)\", y = \"Log(density)\")\n\n\n\n\n\n\n\n## get a vector weeks in ascending order \npeak_weeks <- periodogram(counts, \n case_int, \n start_week = c(2002, 1), \n output = \"weeks\")\n\nNOTE: It is possible to use the above weeks to add them to sin and cosine terms, however we will use a function to generate these terms (see regression section below) \n\n\n\nDecomposition\nClassical decomposition is used to break a time series down several parts, which when taken together make up for the pattern you see. These different parts are:\n\nThe trend-cycle (the long-term direction of the data).\nThe seasonality (repeating patterns).\n\nThe random (what is left after removing trend and season).\n\n\n## decompose the counts dataset \ncounts %>% \n # using an additive classical decomposition model\n model(classical_decomposition(case_int, type = \"additive\")) %>% \n ## extract the important information from the model\n components() %>% \n ## generate a plot \n autoplot()\n\n\n\n\n\n\n\n\n\n\n\nAutocorrelation\nAutocorrelation tells you about the relation between the counts of each week and the weeks before it (called lags).\nUsing the ACF() function, we can produce a plot which shows us a number of lines for the relation at different lags. Where the lag is 0 (x = 0), this line would always be 1 as it shows the relation between an observation and itself (not shown here). The first line shown here (x = 1) shows the relation between each observation and the observation before it (lag of 1), the second shows the relation between each observation and the observation before last (lag of 2) and so on until lag of 52 which shows the relation between each observation and the observation from 1 year (52 weeks before).\nUsing the PACF() function (for partial autocorrelation) shows the same type of relation but adjusted for all other weeks between. This is less informative for determining periodicity.\n\n## using the counts dataset\ncounts %>% \n ## calculate autocorrelation using a full years worth of lags\n ACF(case_int, lag_max = 52) %>% \n ## show a plot\n autoplot()\n\n\n\n\n\n\n\n## using the counts data set \ncounts %>% \n ## calculate the partial autocorrelation using a full years worth of lags\n PACF(case_int, lag_max = 52) %>% \n ## show a plot\n autoplot()\n\n\n\n\n\n\n\n\nYou can formally test the null hypothesis of independence in a time series (i.e.  that it is not autocorrelated) using the Ljung-Box test (in the stats package). A significant p-value suggests that there is autocorrelation in the data.\n\n## test for independance \nBox.test(counts$case_int, type = \"Ljung-Box\")\n\n\n Box-Ljung test\n\ndata: counts$case_int\nX-squared = 462.65, df = 1, p-value < 2.2e-16", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -2023,7 +2023,7 @@ "href": "new_pages/time_series.html#fitting-regressions", "title": "23  Time series and outbreak detection", "section": "23.5 Fitting regressions", - "text": "23.5 Fitting regressions\nIt is possible to fit a large number of different regressions to a time series, however, here we will demonstrate how to fit a negative binomial regression - as this is often the most appropriate for counts data in infectious diseases.\n\n\nFourier terms\nFourier terms are the equivalent of sin and cosin curves. The difference is that these are fit based on finding the most appropriate combination of curves to explain your data.\nIf only fitting one fourier term, this would be the equivalent of fitting a sin and a cosin for your most frequently occurring lag seen in your periodogram (in our case 52 weeks). We use the fourier() function from the forecast package.\nIn the below code we assign using the $, as fourier() returns two columns (one for sin one for cosin) and so these are added to the dataset as a list, called “fourier” - but this list can then be used as a normal variable in regression.\n\n## add in fourier terms using the epiweek and case_int variabless\ncounts$fourier <- select(counts, epiweek, case_int) %>% \n fourier(K = 1)\n\n\n\n\nNegative binomial\nIt is possible to fit regressions using base stats or MASS functions (e.g. lm(), glm() and glm.nb()). However we will be using those from the trending package, as this allows for calculating appropriate confidence and prediction intervals (which are otherwise not available). The syntax is the same, and you specify an outcome variable then a tilde (~) and then add your various exposure variables of interest separated by a plus (+).\nThe other difference is that we first define the model and then fit() it to the data. This is useful because it allows for comparing multiple different models with the same syntax.\nTIP: If you wanted to use rates, rather than counts you could include the population variable as a logarithmic offset term, by adding offset(log(population). You would then need to set population to be 1, before using predict() in order to produce a rate. \nTIP: For fitting more complex models such as ARIMA or prophet, see the fable package.\n\n## define the model you want to fit (negative binomial) \nmodel <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the fourier terms to account for seasonality\n fourier)\n\n## fit your model using the counts dataset\nfitted_model <- trending::fit(model, data.frame(counts))\n\n## calculate confidence intervals and prediction intervals \nobserved <- predict(fitted_model, simulate_pi = FALSE)\n\nestimate_res <- data.frame(observed$result)\n\n## plot your regression \nggplot(data = estimate_res, aes(x = epiweek)) + \n ## add in a line for the model estimate\n geom_line(aes(y = estimate),\n col = \"Red\") + \n ## add in a band for the prediction intervals \n geom_ribbon(aes(ymin = lower_pi, \n ymax = upper_pi), \n alpha = 0.25) + \n ## add in a line for your observed case counts\n geom_line(aes(y = case_int), \n col = \"black\") + \n ## make a traditional plot (with black axes and white background)\n theme_classic()\n\n\n\n\n\n\n\n\n\n\n\nResiduals\nTo see how well our model fits the observed data we need to look at the residuals. The residuals are the difference between the observed counts and the counts estimated from the model. We could calculate this simply by using case_int - estimate, but the residuals() function extracts this directly from the regression for us.\nWhat we see from the below, is that we are not explaining all of the variation that we could with the model. It might be that we should fit more fourier terms, and address the amplitude. However for this example we will leave it as is. The plots show that our model does worse in the peaks and troughs (when counts are at their highest and lowest) and that it might be more likely to underestimate the observed counts.\n\n## calculate the residuals \nestimate_res <- estimate_res %>% \n mutate(resid = fitted_model$result[[1]]$residuals)\n\n## are the residuals fairly constant over time (if not: outbreaks? change in practice?)\nestimate_res %>%\n ggplot(aes(x = epiweek, y = resid)) +\n geom_line() +\n geom_point() + \n labs(x = \"epiweek\", y = \"Residuals\")\n\n\n\n\n\n\n\n## is there autocorelation in the residuals (is there a pattern to the error?) \nestimate_res %>% \n as_tsibble(index = epiweek) %>% \n ACF(resid, lag_max = 52) %>% \n autoplot()\n\n\n\n\n\n\n\n## are residuals normally distributed (are under or over estimating?) \nestimate_res %>%\n ggplot(aes(x = resid)) +\n geom_histogram(binwidth = 100) +\n geom_rug() +\n labs(y = \"count\") \n\n\n\n\n\n\n\n## compare observed counts to their residuals \n ## should also be no pattern \nestimate_res %>%\n ggplot(aes(x = estimate, y = resid)) +\n geom_point() +\n labs(x = \"Fitted\", y = \"Residuals\")\n\n\n\n\n\n\n\n## formally test autocorrelation of the residuals\n## H0 is that residuals are from a white-noise series (i.e. random)\n## test for independence \n## if p value significant then non-random\nBox.test(estimate_res$resid, type = \"Ljung-Box\")\n\n\n Box-Ljung test\n\ndata: estimate_res$resid\nX-squared = 336.25, df = 1, p-value < 2.2e-16", + "text": "23.5 Fitting regressions\nIt is possible to fit a large number of different regressions to a time series, however, here we will demonstrate how to fit a negative binomial regression - as this is often the most appropriate for counts data in infectious diseases.\n\n\nFourier terms\nFourier terms are the equivalent of sine and cosine curves. The difference is that these are fit based on finding the most appropriate combination of curves to explain your data.\nIf only fitting one fourier term, this would be the equivalent of fitting a sine and a cosine for your most frequently occurring lag seen in your periodogram (in our case 52 weeks). We use the fourier() function from the forecast package.\nIn the below code we assign using the $, as fourier() returns two columns (one for sin one for cosin) and so these are added to the dataset as a list, called “fourier” - but this list can then be used as a normal variable in regression.\n\n## add in fourier terms using the epiweek and case_int variabless\ncounts$fourier <- select(counts, epiweek, case_int) %>% \n fourier(K = 1)\n\n\n\n\nNegative binomial\nIt is possible to fit regressions using base stats or MASS functions (e.g. lm(), glm() and glm.nb()). However we will be using those from the trending package, as this allows for calculating appropriate confidence and prediction intervals (which are otherwise not available). The syntax is the same, and you specify an outcome variable then a tilde (~) and then add your various exposure variables of interest separated by a plus (+).\nThe other difference is that we first define the model and then fit() it to the data. This is useful because it allows for comparing multiple different models with the same syntax.\nTIP: If you wanted to use rates, rather than counts you could include the population variable as a logarithmic offset term, by adding offset(log(population). You would then need to set population to be 1, before using predict() in order to produce a rate. \nTIP: For fitting more complex models such as ARIMA or prophet, see the fable package.\n\n## define the model you want to fit (negative binomial) \nmodel <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the fourier terms to account for seasonality\n fourier)\n\n## fit your model using the counts dataset\nfitted_model <- trending::fit(model, data.frame(counts))\n\n## calculate confidence intervals and prediction intervals \nobserved <- predict(fitted_model, simulate_pi = FALSE)\n\nestimate_res <- data.frame(observed$result)\n\n## plot your regression \nggplot(data = estimate_res, aes(x = epiweek)) + \n ## add in a line for the model estimate\n geom_line(aes(y = estimate),\n col = \"Red\") + \n ## add in a band for the prediction intervals \n geom_ribbon(aes(ymin = lower_pi, \n ymax = upper_pi), \n alpha = 0.25) + \n ## add in a line for your observed case counts\n geom_line(aes(y = case_int), \n col = \"black\") + \n ## make a traditional plot (with black axes and white background)\n theme_classic()\n\n\n\n\n\n\n\n\n\n\n\nResiduals\nTo see how well our model fits the observed data we need to look at the residuals. The residuals are the difference between the observed counts and the counts estimated from the model. We could calculate this simply by using case_int - estimate, but the residuals() function extracts this directly from the regression for us.\nWhat we see from the below, is that we are not explaining all of the variation that we could with the model. It might be that we should fit more fourier terms, and address the amplitude. However for this example we will leave it as is. The plots show that our model does worse in the peaks and troughs (when counts are at their highest and lowest) and that it might be more likely to underestimate the observed counts.\n\n## calculate the residuals \nestimate_res <- estimate_res %>% \n mutate(resid = fitted_model$result[[1]]$residuals)\n\n## are the residuals fairly constant over time (if not: outbreaks? change in practice?)\nestimate_res %>%\n ggplot(aes(x = epiweek, y = resid)) +\n geom_line() +\n geom_point() + \n labs(x = \"epiweek\", y = \"Residuals\")\n\n\n\n\n\n\n\n## is there autocorelation in the residuals (is there a pattern to the error?) \nestimate_res %>% \n as_tsibble(index = epiweek) %>% \n ACF(resid, lag_max = 52) %>% \n autoplot()\n\n\n\n\n\n\n\n## are residuals normally distributed (are under or over estimating?) \nestimate_res %>%\n ggplot(aes(x = resid)) +\n geom_histogram(binwidth = 100) +\n geom_rug() +\n labs(y = \"count\") \n\n\n\n\n\n\n\n## compare observed counts to their residuals \n ## should also be no pattern \nestimate_res %>%\n ggplot(aes(x = estimate, y = resid)) +\n geom_point() +\n labs(x = \"Fitted\", y = \"Residuals\")\n\n\n\n\n\n\n\n## formally test autocorrelation of the residuals\n## H0 is that residuals are from a white-noise series (i.e. random)\n## test for independence \n## if p value significant then non-random\nBox.test(estimate_res$resid, type = \"Ljung-Box\")\n\n\n Box-Ljung test\n\ndata: estimate_res$resid\nX-squared = 336.25, df = 1, p-value < 2.2e-16", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -2045,7 +2045,7 @@ "href": "new_pages/time_series.html#outbreak-detection", "title": "23  Time series and outbreak detection", "section": "23.7 Outbreak detection", - "text": "23.7 Outbreak detection\nWe will demonstrate two (similar) methods of detecting outbreaks here. The first builds on the sections above. We use the trending package to fit regressions to previous years, and then predict what we expect to see in the following year. If observed counts are above what we expect, then it could suggest there is an outbreak. The second method is based on similar principles but uses the surveillance package, which has a number of different algorithms for aberration detection.\nCAUTION: Normally, you are interested in the current year (where you only know counts up to the present week). So in this example we are pretending to be in week 39 of 2011.\n\n\ntrending package\nFor this method we define a baseline (which should usually be about 5 years of data). We fit a regression to the baseline data, and then use that to predict the estimates for the next year.\n\n\nCut-off date\nIt is easier to define your dates in one place and then use these throughout the rest of your code.\nHere we define a start date (when our observations started) and a cut-off date (the end of our baseline period - and when the period we want to predict for starts). ~We also define how many weeks are in our year of interest (the one we are going to be predicting)~. We also define how many weeks are between our baseline cut-off and the end date that we are interested in predicting for.\nNOTE: In this example we pretend to currently be at the end of September 2011 (“2011 W39”).\n\n## define start date (when observations began)\nstart_date <- min(counts$epiweek)\n\n## define a cut-off week (end of baseline, start of prediction period)\ncut_off <- yearweek(\"2010-12-31\")\n\n## define the last date interested in (i.e. end of prediction)\nend_date <- yearweek(\"2011-12-31\")\n\n## find how many weeks in period (year) of interest\nnum_weeks <- as.numeric(end_date - cut_off)\n\n\n\n\nAdd rows\nTo be able to forecast in a tidyverse format, we need to have the right number of rows in our dataset, i.e. one row for each week up to the end_datedefined above. The code below allows you to add these rows for by a grouping variable - for example if we had multiple countries in one dataset, we could group by country and then add rows appropriately for each. The group_by_key() function from tsibble allows us to do this grouping and then pass the grouped data to dplyr functions, group_modify() and add_row(). Then we specify the sequence of weeks between one after the maximum week currently available in the data and the end week.\n\n## add in missing weeks till end of year \ncounts <- counts %>%\n ## group by the region\n group_by_key() %>%\n ## for each group add rows from the highest epiweek to the end of year\n group_modify(~add_row(.,\n epiweek = seq(max(.$epiweek) + 1, \n end_date,\n by = 1)))\n\n\n\n\nFourier terms\nWe need to redefine our fourier terms - as we want to fit them to the baseline date only and then predict (extrapolate) those terms for the next year. To do this we need to combine two output lists from the fourier() function together; the first one is for the baseline data, and the second one predicts for the year of interest (by defining the h argument).\nN.b. to bind rows we have to use rbind() (rather than tidyverse bind_rows) as the fourier columns are a list (so not named individually).\n\n## define fourier terms (sincos) \ncounts <- counts %>% \n mutate(\n ## combine fourier terms for weeks prior to and after 2010 cut-off date\n ## (nb. 2011 fourier terms are predicted)\n fourier = rbind(\n ## get fourier terms for previous years\n fourier(\n ## only keep the rows before 2011\n filter(counts, \n epiweek <= cut_off), \n ## include one set of sin cos terms \n K = 1\n ), \n ## predict the fourier terms for 2011 (using baseline data)\n fourier(\n ## only keep the rows before 2011\n filter(counts, \n epiweek <= cut_off),\n ## include one set of sin cos terms \n K = 1, \n ## predict 52 weeks ahead\n h = num_weeks\n )\n )\n )\n\n\n\n\nSplit data and fit regression\nWe now have to split our dataset in to the baseline period and the prediction period. This is done using the dplyr group_split() function after group_by(), and will create a list with two data frames, one for before your cut-off and one for after.\nWe then use the purrr package pluck() function to pull the datasets out of the list (equivalent of using square brackets, e.g. dat[[1]]), and can then fit our model to the baseline data, and then use the predict() function for our data of interest after the cut-off.\nSee the page on Iteration, loops, and lists to learn more about purrr.\nCAUTION: Note the use of simulate_pi = FALSE within the predict() argument. This is because the default behaviour of trending is to use the ciTools package to estimate a prediction interval. This does not work if there are NA counts, and also produces more granular intervals. See ?trending::predict.trending_model_fit for details. \n\n# split data for fitting and prediction\ndat <- counts %>% \n group_by(epiweek <= cut_off) %>%\n group_split()\n\n## define the model you want to fit (negative binomial) \nmodel <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the furier terms to account for seasonality\n fourier\n)\n\n# define which data to use for fitting and which for predicting\nfitting_data <- pluck(dat, 2)\npred_data <- pluck(dat, 1) %>% \n select(case_int, epiweek, fourier)\n\n# fit model \nfitted_model <- trending::fit(model, data.frame(fitting_data))\n\n# get confint and estimates for fitted data\nobserved <- fitted_model %>% \n predict(simulate_pi = FALSE)\n\n# forecast with data want to predict with \nforecasts <- fitted_model %>% \n predict(data.frame(pred_data), simulate_pi = FALSE)\n\n## combine baseline and predicted datasets\nobserved <- bind_rows(observed$result, forecasts$result)\n\nAs previously, we can visualise our model with ggplot. We highlight alerts with red dots for observed counts above the 95% prediction interval. This time we also add a vertical line to label when the forecast starts.\n\n## plot your regression \nggplot(data = observed, aes(x = epiweek)) + \n ## add in a line for the model estimate\n geom_line(aes(y = estimate),\n col = \"grey\") + \n ## add in a band for the prediction intervals \n geom_ribbon(aes(ymin = lower_pi, \n ymax = upper_pi), \n alpha = 0.25) + \n ## add in a line for your observed case counts\n geom_line(aes(y = case_int), \n col = \"black\") + \n ## plot in points for the observed counts above expected\n geom_point(\n data = filter(observed, case_int > upper_pi), \n aes(y = case_int), \n colour = \"red\", \n size = 2) + \n ## add vertical line and label to show where forecasting started\n geom_vline(\n xintercept = as.Date(cut_off), \n linetype = \"dashed\") + \n annotate(geom = \"text\", \n label = \"Forecast\", \n x = cut_off, \n y = max(observed$upper_pi) - 250, \n angle = 90, \n vjust = 1\n ) + \n ## make a traditional plot (with black axes and white background)\n theme_classic()\n\nWarning: Removed 13 rows containing missing values or values outside the scale range\n(`geom_line()`).\n\n\n\n\n\n\n\n\n\n\n\n\nPrediction validation\nBeyond inspecting residuals, it is important to investigate how good your model is at predicting cases in the future. This gives you an idea of how reliable your threshold alerts are.\nThe traditional way of validating is to see how well you can predict the latest year before the present one (because you don’t yet know the counts for the “current year”). For example in our data set we would use the data from 2002 to 2009 to predict 2010, and then see how accurate those predictions are. Then refit the model to include 2010 data and use that to predict 2011 counts.\nAs can be seen in the figure below by Hyndman et al in “Forecasting principles and practice”.\n figure reproduced with permission from the authors\nThe downside of this is that you are not using all the data available to you, and it is not the final model that you are using for prediction.\nAn alternative is to use a method called cross-validation. In this scenario you roll over all of the data available to fit multiple models to predict one year ahead. You use more and more data in each model, as seen in the figure below from the same [Hyndman et al text]((https://otexts.com/fpp3/). For example, the first model uses 2002 to predict 2003, the second uses 2002 and 2003 to predict 2004, and so on. figure reproduced with permission from the authors\nIn the below we use purrr package map() function to loop over each dataset. We then put estimates in one data set and merge with the original case counts, to use the yardstick package to compute measures of accuracy. We compute four measures including: Root mean squared error (RMSE), Mean absolute error (MAE), Mean absolute scaled error (MASE), Mean absolute percent error (MAPE).\nCAUTION: Note the use of simulate_pi = FALSE within the predict() argument. This is because the default behaviour of trending is to use the ciTools package to estimate a prediction interval. This does not work if there are NA counts, and also produces more granular intervals. See ?trending::predict.trending_model_fit for details. \n\n## Cross validation: predicting week(s) ahead based on sliding window\n\n## expand your data by rolling over in 52 week windows (before + after) \n## to predict 52 week ahead\n## (creates longer and longer chains of observations - keeps older data)\n\n## define window want to roll over\nroll_window <- 52\n\n## define weeks ahead want to predict \nweeks_ahead <- 52\n\n## create a data set of repeating, increasingly long data\n## label each data set with a unique id\n## only use cases before year of interest (i.e. 2011)\ncase_roll <- counts %>% \n filter(epiweek < cut_off) %>% \n ## only keep the week and case counts variables\n select(epiweek, case_int) %>% \n ## drop the last x observations \n ## depending on how many weeks ahead forecasting \n ## (otherwise will be an actual forecast to \"unknown\")\n slice(1:(n() - weeks_ahead)) %>%\n as_tsibble(index = epiweek) %>% \n ## roll over each week in x after windows to create grouping ID \n ## depending on what rolling window specify\n stretch_tsibble(.init = roll_window, .step = 1) %>% \n ## drop the first couple - as have no \"before\" cases\n filter(.id > roll_window)\n\n\n## for each of the unique data sets run the code below\nforecasts <- purrr::map(unique(case_roll$.id), \n function(i) {\n \n ## only keep the current fold being fit \n mini_data <- filter(case_roll, .id == i) %>% \n as_tibble()\n \n ## create an empty data set for forecasting on \n forecast_data <- tibble(\n epiweek = seq(max(mini_data$epiweek) + 1,\n max(mini_data$epiweek) + weeks_ahead,\n by = 1),\n case_int = rep.int(NA, weeks_ahead),\n .id = rep.int(i, weeks_ahead)\n )\n \n ## add the forecast data to the original \n mini_data <- bind_rows(mini_data, forecast_data)\n \n ## define the cut off based on latest non missing count data \n cv_cut_off <- mini_data %>% \n ## only keep non-missing rows\n drop_na(case_int) %>% \n ## get the latest week\n summarise(max(epiweek)) %>% \n ## extract so is not in a dataframe\n pull()\n \n ## make mini_data back in to a tsibble\n mini_data <- tsibble(mini_data, index = epiweek)\n \n ## define fourier terms (sincos) \n mini_data <- mini_data %>% \n mutate(\n ## combine fourier terms for weeks prior to and after cut-off date\n fourier = rbind(\n ## get fourier terms for previous years\n forecast::fourier(\n ## only keep the rows before cut-off\n filter(mini_data, \n epiweek <= cv_cut_off), \n ## include one set of sin cos terms \n K = 1\n ), \n ## predict the fourier terms for following year (using baseline data)\n fourier(\n ## only keep the rows before cut-off\n filter(mini_data, \n epiweek <= cv_cut_off),\n ## include one set of sin cos terms \n K = 1, \n ## predict 52 weeks ahead\n h = weeks_ahead\n )\n )\n )\n \n \n # split data for fitting and prediction\n dat <- mini_data %>% \n group_by(epiweek <= cv_cut_off) %>%\n group_split()\n\n ## define the model you want to fit (negative binomial) \n model <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the furier terms to account for seasonality\n fourier\n )\n\n # define which data to use for fitting and which for predicting\n fitting_data <- pluck(dat, 2)\n pred_data <- pluck(dat, 1)\n \n # fit model \n fitted_model <- trending::fit(model, fitting_data)\n \n # forecast with data want to predict with \n forecasts <- fitted_model %>% \n predict(data.frame(pred_data), simulate_pi = FALSE)\n forecasts <- data.frame(forecasts$result[[1]]) %>% \n ## only keep the week and the forecast estimate\n select(epiweek, estimate)\n \n }\n )\n\n## make the list in to a data frame with all the forecasts\nforecasts <- bind_rows(forecasts)\n\n## join the forecasts with the observed\nforecasts <- left_join(forecasts, \n select(counts, epiweek, case_int),\n by = \"epiweek\")\n\n## using {yardstick} compute metrics\n ## RMSE: Root mean squared error\n ## MAE: Mean absolute error \n ## MASE: Mean absolute scaled error\n ## MAPE: Mean absolute percent error\nmodel_metrics <- bind_rows(\n ## in your forcasted dataset compare the observed to the predicted\n rmse(forecasts, case_int, estimate), \n mae( forecasts, case_int, estimate),\n mase(forecasts, case_int, estimate),\n mape(forecasts, case_int, estimate),\n ) %>% \n ## only keep the metric type and its output\n select(Metric = .metric, \n Measure = .estimate) %>% \n ## make in to wide format so can bind rows after\n pivot_wider(names_from = Metric, values_from = Measure)\n\n## return model metrics \nmodel_metrics\n\n# A tibble: 1 × 4\n rmse mae mase mape\n <dbl> <dbl> <dbl> <dbl>\n1 252. 199. 1.96 17.3\n\n\n\n\n\n\nsurveillance package\nIn this section we use the surveillance package to create alert thresholds based on outbreak detection algorithms. There are several different methods available in the package, however we will focus on two options here. For details, see these papers on the application and theory of the alogirthms used.\nThe first option uses the improved Farrington method. This fits a negative binomial glm (including trend) and down-weights past outbreaks (outliers) to create a threshold level.\nThe second option use the glrnb method. This also fits a negative binomial glm but includes trend and fourier terms (so is favoured here). The regression is used to calculate the “control mean” (~fitted values) - it then uses a computed generalized likelihood ratio statistic to assess if there is shift in the mean for each week. Note that the threshold for each week takes in to account previous weeks so if there is a sustained shift an alarm will be triggered. (Also note that after each alarm the algorithm is reset)\nIn order to work with the surveillance package, we first need to define a “surveillance time series” object (using the sts() function) to fit within the framework.\n\n## define surveillance time series object\n## nb. you can include a denominator with the population object (see ?sts)\ncounts_sts <- sts(observed = counts$case_int[!is.na(counts$case_int)],\n start = c(\n ## subset to only keep the year from start_date \n as.numeric(str_sub(start_date, 1, 4)), \n ## subset to only keep the week from start_date\n as.numeric(str_sub(start_date, 7, 8))), \n ## define the type of data (in this case weekly)\n freq = 52)\n\n## define the week range that you want to include (ie. prediction period)\n## nb. the sts object only counts observations without assigning a week or \n## year identifier to them - so we use our data to define the appropriate observations\nweekrange <- cut_off - start_date\n\n\n\nFarrington method\nWe then define each of our parameters for the Farrington method in a list. Then we run the algorithm using farringtonFlexible() and then we can extract the threshold for an alert using farringtonmethod@upperboundto include this in our dataset. It is also possible to extract a TRUE/FALSE for each week if it triggered an alert (was above the threshold) using farringtonmethod@alarm.\n\n## define control\nctrl <- list(\n ## define what time period that want threshold for (i.e. 2011)\n range = which(counts_sts@epoch > weekrange),\n b = 9, ## how many years backwards for baseline\n w = 2, ## rolling window size in weeks\n weightsThreshold = 2.58, ## reweighting past outbreaks (improved noufaily method - original suggests 1)\n ## pastWeeksNotIncluded = 3, ## use all weeks available (noufaily suggests drop 26)\n trend = TRUE,\n pThresholdTrend = 1, ## 0.05 normally, however 1 is advised in the improved method (i.e. always keep)\n thresholdMethod = \"nbPlugin\",\n populationOffset = TRUE\n )\n\n## apply farrington flexible method\nfarringtonmethod <- farringtonFlexible(counts_sts, ctrl)\n\n## create a new variable in the original dataset called threshold\n## containing the upper bound from farrington \n## nb. this is only for the weeks in 2011 (so need to subset rows)\ncounts[which(counts$epiweek >= cut_off & \n !is.na(counts$case_int)),\n \"threshold\"] <- farringtonmethod@upperbound\n\nWe can then visualise the results in ggplot as done previously.\n\nggplot(counts, aes(x = epiweek)) + \n ## add in observed case counts as a line\n geom_line(aes(y = case_int, colour = \"Observed\")) + \n ## add in upper bound of aberration algorithm\n geom_line(aes(y = threshold, colour = \"Alert threshold\"), \n linetype = \"dashed\", \n size = 1.5) +\n ## define colours\n scale_colour_manual(values = c(\"Observed\" = \"black\", \n \"Alert threshold\" = \"red\")) + \n ## make a traditional plot (with black axes and white background)\n theme_classic() + \n ## remove title of legend \n theme(legend.title = element_blank())\n\n\n\n\n\n\n\n\n\n\n\nGLRNB method\nSimilarly for the GLRNB method we define each of our parameters for the in a list, then fit the algorithm and extract the upper bounds.\nCAUTION: This method uses “brute force” (similar to bootstrapping) for calculating thresholds, so can take a long time!\nSee the GLRNB vignette for details.\n\n## define control options\nctrl <- list(\n ## define what time period that want threshold for (i.e. 2011)\n range = which(counts_sts@epoch > weekrange),\n mu0 = list(S = 1, ## number of fourier terms (harmonics) to include\n trend = TRUE, ## whether to include trend or not\n refit = FALSE), ## whether to refit model after each alarm\n ## cARL = threshold for GLR statistic (arbitrary)\n ## 3 ~ middle ground for minimising false positives\n ## 1 fits to the 99%PI of glm.nb - with changes after peaks (threshold lowered for alert)\n c.ARL = 2,\n # theta = log(1.5), ## equates to a 50% increase in cases in an outbreak\n ret = \"cases\" ## return threshold upperbound as case counts\n )\n\n## apply the glrnb method\nglrnbmethod <- glrnb(counts_sts, control = ctrl, verbose = FALSE)\n\n## create a new variable in the original dataset called threshold\n## containing the upper bound from glrnb \n## nb. this is only for the weeks in 2011 (so need to subset rows)\ncounts[which(counts$epiweek >= cut_off & \n !is.na(counts$case_int)),\n \"threshold_glrnb\"] <- glrnbmethod@upperbound\n\nVisualise the outputs as previously.\n\nggplot(counts, aes(x = epiweek)) + \n ## add in observed case counts as a line\n geom_line(aes(y = case_int, colour = \"Observed\")) + \n ## add in upper bound of aberration algorithm\n geom_line(aes(y = threshold_glrnb, colour = \"Alert threshold\"), \n linetype = \"dashed\", \n size = 1.5) +\n ## define colours\n scale_colour_manual(values = c(\"Observed\" = \"black\", \n \"Alert threshold\" = \"red\")) + \n ## make a traditional plot (with black axes and white background)\n theme_classic() + \n ## remove title of legend \n theme(legend.title = element_blank())", + "text": "23.7 Outbreak detection\nWe will demonstrate two (similar) methods of detecting outbreaks here. The first builds on the sections above. We use the trending package to fit regressions to previous years, and then predict what we expect to see in the following year. If observed counts are above what we expect, then it could suggest there is an outbreak. The second method is based on similar principles but uses the surveillance package, which has a number of different algorithms for aberration detection.\nCAUTION: Normally, you are interested in the current year (where you only know counts up to the present week). So in this example we are pretending to be in week 39 of 2011.\n\n\ntrending package\nFor this method we define a baseline (which should usually be about 5 years of data). We fit a regression to the baseline data, and then use that to predict the estimates for the next year.\n\n\nCut-off date\nIt is easier to define your dates in one place and then use these throughout the rest of your code.\nHere we define a start date (when our observations started) and a cut-off date (the end of our baseline period - and when the period we want to predict for starts). ~We also define how many weeks are in our year of interest (the one we are going to be predicting)~. We also define how many weeks are between our baseline cut-off and the end date that we are interested in predicting for.\nNOTE: In this example we pretend to currently be at the end of September 2011 (“2011 W39”).\n\n## define start date (when observations began)\nstart_date <- min(counts$epiweek)\n\n## define a cut-off week (end of baseline, start of prediction period)\ncut_off <- yearweek(\"2010-12-31\")\n\n## define the last date interested in (i.e. end of prediction)\nend_date <- yearweek(\"2011-12-31\")\n\n## find how many weeks in period (year) of interest\nnum_weeks <- as.numeric(end_date - cut_off)\n\n\n\n\nAdd rows\nTo be able to forecast in a tidyverse format, we need to have the right number of rows in our dataset, i.e. one row for each week up to the end_datedefined above. The code below allows you to add these rows for by a grouping variable - for example if we had multiple countries in one dataset, we could group by country and then add rows appropriately for each. The group_by_key() function from tsibble allows us to do this grouping and then pass the grouped data to dplyr functions, group_modify() and add_row(). Then we specify the sequence of weeks between one after the maximum week currently available in the data and the end week.\n\n## add in missing weeks till end of year \ncounts <- counts %>%\n ## group by the region\n group_by_key() %>%\n ## for each group add rows from the highest epiweek to the end of year\n group_modify(~add_row(.,\n epiweek = seq(max(.$epiweek) + 1, \n end_date,\n by = 1)))\n\n\n\n\nFourier terms\nWe need to redefine our fourier terms - as we want to fit them to the baseline date only and then predict (extrapolate) those terms for the next year. To do this we need to combine two output lists from the fourier() function together; the first one is for the baseline data, and the second one predicts for the year of interest (by defining the h argument).\nN.b. to bind rows we have to use rbind() (rather than tidyverse bind_rows) as the fourier columns are a list (so not named individually).\n\n## define fourier terms (sincos) \ncounts <- counts %>% \n mutate(\n ## combine fourier terms for weeks prior to and after 2010 cut-off date\n ## (nb. 2011 fourier terms are predicted)\n fourier = rbind(\n ## get fourier terms for previous years\n fourier(\n ## only keep the rows before 2011\n filter(counts, \n epiweek <= cut_off), \n ## include one set of sin cos terms \n K = 1\n ), \n ## predict the fourier terms for 2011 (using baseline data)\n fourier(\n ## only keep the rows before 2011\n filter(counts, \n epiweek <= cut_off),\n ## include one set of sin cos terms \n K = 1, \n ## predict 52 weeks ahead\n h = num_weeks\n )\n )\n )\n\n\n\n\nSplit data and fit regression\nWe now have to split our dataset in to the baseline period and the prediction period. This is done using the dplyr group_split() function after group_by(), and will create a list with two data frames, one for before your cut-off and one for after.\nWe then use the purrr package pluck() function to pull the datasets out of the list (equivalent of using square brackets, e.g. dat[[1]]), and can then fit our model to the baseline data, and then use the predict() function for our data of interest after the cut-off.\nSee the page on Iteration, loops, and lists to learn more about purrr.\nCAUTION: Note the use of simulate_pi = FALSE within the predict() argument. This is because the default behaviour of trending is to use the ciTools package to estimate a prediction interval. This does not work if there are NA counts, and also produces more granular intervals. See ?trending::predict.trending_model_fit for details. \n\n# split data for fitting and prediction\ndat <- counts %>% \n group_by(epiweek <= cut_off) %>%\n group_split()\n\n## define the model you want to fit (negative binomial) \nmodel <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the furier terms to account for seasonality\n fourier\n)\n\n# define which data to use for fitting and which for predicting\nfitting_data <- pluck(dat, 2)\npred_data <- pluck(dat, 1) %>% \n select(case_int, epiweek, fourier)\n\n# fit model \nfitted_model <- trending::fit(model, data.frame(fitting_data))\n\n# get confint and estimates for fitted data\nobserved <- fitted_model %>% \n predict(simulate_pi = FALSE)\n\n# forecast with data want to predict with \nforecasts <- fitted_model %>% \n predict(data.frame(pred_data), simulate_pi = FALSE)\n\n## combine baseline and predicted datasets\nobserved <- bind_rows(observed$result, forecasts$result)\n\nAs previously, we can visualise our model with ggplot. We highlight alerts with red dots for observed counts above the 95% prediction interval. This time we also add a vertical line to label when the forecast starts.\n\n## plot your regression \nggplot(data = observed, aes(x = epiweek)) + \n ## add in a line for the model estimate\n geom_line(aes(y = estimate),\n col = \"grey\") + \n ## add in a band for the prediction intervals \n geom_ribbon(aes(ymin = lower_pi, \n ymax = upper_pi), \n alpha = 0.25) + \n ## add in a line for your observed case counts\n geom_line(aes(y = case_int), \n col = \"black\") + \n ## plot in points for the observed counts above expected\n geom_point(\n data = filter(observed, case_int > upper_pi), \n aes(y = case_int), \n colour = \"red\", \n size = 2) + \n ## add vertical line and label to show where forecasting started\n geom_vline(\n xintercept = as.Date(cut_off), \n linetype = \"dashed\") + \n annotate(geom = \"text\", \n label = \"Forecast\", \n x = cut_off, \n y = max(observed$upper_pi) - 250, \n angle = 90, \n vjust = 1\n ) + \n ## make a traditional plot (with black axes and white background)\n theme_classic()\n\nWarning: Removed 13 rows containing missing values or values outside the scale range\n(`geom_line()`).\n\n\n\n\n\n\n\n\n\n\n\n\nPrediction validation\nBeyond inspecting residuals, it is important to investigate how good your model is at predicting cases in the future. This gives you an idea of how reliable your threshold alerts are.\nThe traditional way of validating is to see how well you can predict the latest year before the present one (because you don’t yet know the counts for the “current year”). For example in our data set we would use the data from 2002 to 2009 to predict 2010, and then see how accurate those predictions are. Then refit the model to include 2010 data and use that to predict 2011 counts.\nAs can be seen in the figure below by Hyndman et al in “Forecasting principles and practice”.\n figure reproduced with permission from the authors\nThe downside of this is that you are not using all the data available to you, and it is not the final model that you are using for prediction.\nAn alternative is to use a method called cross-validation. In this scenario you roll over all of the data available to fit multiple models to predict one year ahead. You use more and more data in each model, as seen in the figure below from the same Hyndman et al.,.\nFor example, the first model uses 2002 to predict 2003, the second uses 2002 and 2003 to predict 2004, and so on. figure reproduced with permission from the authors\nIn the below we use purrr package map() function to loop over each dataset. We then put estimates in one data set and merge with the original case counts, to use the yardstick package to compute measures of accuracy. We compute four measures including: Root mean squared error (RMSE), Mean absolute error (MAE), Mean absolute scaled error (MASE), Mean absolute percent error (MAPE).\nCAUTION: Note the use of simulate_pi = FALSE within the predict() argument. This is because the default behaviour of trending is to use the ciTools package to estimate a prediction interval. This does not work if there are NA counts, and also produces more granular intervals. See ?trending::predict.trending_model_fit for details. \n\n## Cross validation: predicting week(s) ahead based on sliding window\n\n## expand your data by rolling over in 52 week windows (before + after) \n## to predict 52 week ahead\n## (creates longer and longer chains of observations - keeps older data)\n\n## define window want to roll over\nroll_window <- 52\n\n## define weeks ahead want to predict \nweeks_ahead <- 52\n\n## create a data set of repeating, increasingly long data\n## label each data set with a unique id\n## only use cases before year of interest (i.e. 2011)\ncase_roll <- counts %>% \n filter(epiweek < cut_off) %>% \n ## only keep the week and case counts variables\n select(epiweek, case_int) %>% \n ## drop the last x observations \n ## depending on how many weeks ahead forecasting \n ## (otherwise will be an actual forecast to \"unknown\")\n slice(1:(n() - weeks_ahead)) %>%\n as_tsibble(index = epiweek) %>% \n ## roll over each week in x after windows to create grouping ID \n ## depending on what rolling window specify\n stretch_tsibble(.init = roll_window, .step = 1) %>% \n ## drop the first couple - as have no \"before\" cases\n filter(.id > roll_window)\n\n\n## for each of the unique data sets run the code below\nforecasts <- purrr::map(unique(case_roll$.id), \n function(i) {\n \n ## only keep the current fold being fit \n mini_data <- filter(case_roll, .id == i) %>% \n as_tibble()\n \n ## create an empty data set for forecasting on \n forecast_data <- tibble(\n epiweek = seq(max(mini_data$epiweek) + 1,\n max(mini_data$epiweek) + weeks_ahead,\n by = 1),\n case_int = rep.int(NA, weeks_ahead),\n .id = rep.int(i, weeks_ahead)\n )\n \n ## add the forecast data to the original \n mini_data <- bind_rows(mini_data, forecast_data)\n \n ## define the cut off based on latest non missing count data \n cv_cut_off <- mini_data %>% \n ## only keep non-missing rows\n drop_na(case_int) %>% \n ## get the latest week\n summarise(max(epiweek)) %>% \n ## extract so is not in a dataframe\n pull()\n \n ## make mini_data back in to a tsibble\n mini_data <- tsibble(mini_data, index = epiweek)\n \n ## define fourier terms (sincos) \n mini_data <- mini_data %>% \n mutate(\n ## combine fourier terms for weeks prior to and after cut-off date\n fourier = rbind(\n ## get fourier terms for previous years\n forecast::fourier(\n ## only keep the rows before cut-off\n filter(mini_data, \n epiweek <= cv_cut_off), \n ## include one set of sin cos terms \n K = 1\n ), \n ## predict the fourier terms for following year (using baseline data)\n fourier(\n ## only keep the rows before cut-off\n filter(mini_data, \n epiweek <= cv_cut_off),\n ## include one set of sin cos terms \n K = 1, \n ## predict 52 weeks ahead\n h = weeks_ahead\n )\n )\n )\n \n \n # split data for fitting and prediction\n dat <- mini_data %>% \n group_by(epiweek <= cv_cut_off) %>%\n group_split()\n\n ## define the model you want to fit (negative binomial) \n model <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the furier terms to account for seasonality\n fourier\n )\n\n # define which data to use for fitting and which for predicting\n fitting_data <- pluck(dat, 2)\n pred_data <- pluck(dat, 1)\n \n # fit model \n fitted_model <- trending::fit(model, fitting_data)\n \n # forecast with data want to predict with \n forecasts <- fitted_model %>% \n predict(data.frame(pred_data), simulate_pi = FALSE)\n forecasts <- data.frame(forecasts$result[[1]]) %>% \n ## only keep the week and the forecast estimate\n select(epiweek, estimate)\n \n }\n )\n\n## make the list in to a data frame with all the forecasts\nforecasts <- bind_rows(forecasts)\n\n## join the forecasts with the observed\nforecasts <- left_join(forecasts, \n select(counts, epiweek, case_int),\n by = \"epiweek\")\n\n## using {yardstick} compute metrics\n ## RMSE: Root mean squared error\n ## MAE: Mean absolute error \n ## MASE: Mean absolute scaled error\n ## MAPE: Mean absolute percent error\nmodel_metrics <- bind_rows(\n ## in your forcasted dataset compare the observed to the predicted\n rmse(forecasts, case_int, estimate), \n mae( forecasts, case_int, estimate),\n mase(forecasts, case_int, estimate),\n mape(forecasts, case_int, estimate),\n ) %>% \n ## only keep the metric type and its output\n select(Metric = .metric, \n Measure = .estimate) %>% \n ## make in to wide format so can bind rows after\n pivot_wider(names_from = Metric, values_from = Measure)\n\n## return model metrics \nmodel_metrics\n\n# A tibble: 1 × 4\n rmse mae mase mape\n <dbl> <dbl> <dbl> <dbl>\n1 252. 199. 1.96 17.3\n\n\n\n\n\n\nsurveillance package\nIn this section we use the surveillance package to create alert thresholds based on outbreak detection algorithms. There are several different methods available in the package, however we will focus on two options here. For details, see these papers on the application and theory of the alogirthms used.\nThe first option uses the improved Farrington method. This fits a negative binomial glm (including trend) and down-weights past outbreaks (outliers) to create a threshold level.\nThe second option use the glrnb method. This also fits a negative binomial glm but includes trend and fourier terms (so is favoured here). The regression is used to calculate the “control mean” (~fitted values) - it then uses a computed generalized likelihood ratio statistic to assess if there is shift in the mean for each week. Note that the threshold for each week takes in to account previous weeks so if there is a sustained shift an alarm will be triggered. (Also note that after each alarm the algorithm is reset).\nIn order to work with the surveillance package, we first need to define a “surveillance time series” object (using the sts() function) to fit within the framework.\n\n## define surveillance time series object\n## nb. you can include a denominator with the population object (see ?sts)\ncounts_sts <- sts(observed = counts$case_int[!is.na(counts$case_int)],\n start = c(\n ## subset to only keep the year from start_date \n as.numeric(str_sub(start_date, 1, 4)), \n ## subset to only keep the week from start_date\n as.numeric(str_sub(start_date, 7, 8))), \n ## define the type of data (in this case weekly)\n freq = 52)\n\n## define the week range that you want to include (ie. prediction period)\n## nb. the sts object only counts observations without assigning a week or \n## year identifier to them - so we use our data to define the appropriate observations\nweekrange <- cut_off - start_date\n\n\n\nFarrington method\nWe then define each of our parameters for the Farrington method in a list. Then we run the algorithm using farringtonFlexible() and then we can extract the threshold for an alert using farringtonmethod@upperboundto include this in our dataset. It is also possible to extract a TRUE/FALSE for each week if it triggered an alert (was above the threshold) using farringtonmethod@alarm.\n\n## define control\nctrl <- list(\n ## define what time period that want threshold for (i.e. 2011)\n range = which(counts_sts@epoch > weekrange),\n b = 9, ## how many years backwards for baseline\n w = 2, ## rolling window size in weeks\n weightsThreshold = 2.58, ## reweighting past outbreaks (improved noufaily method - original suggests 1)\n ## pastWeeksNotIncluded = 3, ## use all weeks available (noufaily suggests drop 26)\n trend = TRUE,\n pThresholdTrend = 1, ## 0.05 normally, however 1 is advised in the improved method (i.e. always keep)\n thresholdMethod = \"nbPlugin\",\n populationOffset = TRUE\n )\n\n## apply farrington flexible method\nfarringtonmethod <- farringtonFlexible(counts_sts, ctrl)\n\n## create a new variable in the original dataset called threshold\n## containing the upper bound from farrington \n## nb. this is only for the weeks in 2011 (so need to subset rows)\ncounts[which(counts$epiweek >= cut_off & \n !is.na(counts$case_int)),\n \"threshold\"] <- farringtonmethod@upperbound\n\nWe can then visualise the results in ggplot as done previously.\n\nggplot(counts, aes(x = epiweek)) + \n ## add in observed case counts as a line\n geom_line(aes(y = case_int, colour = \"Observed\")) + \n ## add in upper bound of aberration algorithm\n geom_line(aes(y = threshold, colour = \"Alert threshold\"), \n linetype = \"dashed\", \n size = 1.5) +\n ## define colours\n scale_colour_manual(values = c(\"Observed\" = \"black\", \n \"Alert threshold\" = \"red\")) + \n ## make a traditional plot (with black axes and white background)\n theme_classic() + \n ## remove title of legend \n theme(legend.title = element_blank())\n\n\n\n\n\n\n\n\n\n\n\nGLRNB method\nSimilarly for the GLRNB method we define each of our parameters for the in a list, then fit the algorithm and extract the upper bounds.\nCAUTION: This method uses “brute force” (similar to bootstrapping) for calculating thresholds, so can take a long time!\nSee the GLRNB vignette for details.\n\n## define control options\nctrl <- list(\n ## define what time period that want threshold for (i.e. 2011)\n range = which(counts_sts@epoch > weekrange),\n mu0 = list(S = 1, ## number of fourier terms (harmonics) to include\n trend = TRUE, ## whether to include trend or not\n refit = FALSE), ## whether to refit model after each alarm\n ## cARL = threshold for GLR statistic (arbitrary)\n ## 3 ~ middle ground for minimising false positives\n ## 1 fits to the 99%PI of glm.nb - with changes after peaks (threshold lowered for alert)\n c.ARL = 2,\n # theta = log(1.5), ## equates to a 50% increase in cases in an outbreak\n ret = \"cases\" ## return threshold upperbound as case counts\n )\n\n## apply the glrnb method\nglrnbmethod <- glrnb(counts_sts, control = ctrl, verbose = FALSE)\n\n## create a new variable in the original dataset called threshold\n## containing the upper bound from glrnb \n## nb. this is only for the weeks in 2011 (so need to subset rows)\ncounts[which(counts$epiweek >= cut_off & \n !is.na(counts$case_int)),\n \"threshold_glrnb\"] <- glrnbmethod@upperbound\n\nVisualise the outputs as previously.\n\nggplot(counts, aes(x = epiweek)) + \n ## add in observed case counts as a line\n geom_line(aes(y = case_int, colour = \"Observed\")) + \n ## add in upper bound of aberration algorithm\n geom_line(aes(y = threshold_glrnb, colour = \"Alert threshold\"), \n linetype = \"dashed\", \n size = 1.5) +\n ## define colours\n scale_colour_manual(values = c(\"Observed\" = \"black\", \n \"Alert threshold\" = \"red\")) + \n ## make a traditional plot (with black axes and white background)\n theme_classic() + \n ## remove title of legend \n theme(legend.title = element_blank())", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -2056,7 +2056,7 @@ "href": "new_pages/time_series.html#interrupted-timeseries", "title": "23  Time series and outbreak detection", "section": "23.8 Interrupted timeseries", - "text": "23.8 Interrupted timeseries\nInterrupted timeseries (also called segmented regression or intervention analysis), is often used in assessing the impact of vaccines on the incidence of disease. But it can be used for assessing impact of a wide range of interventions or introductions. For example changes in hospital procedures or the introduction of a new disease strain to a population. In this example we will pretend that a new strain of Campylobacter was introduced to Germany at the end of 2008, and see if that affects the number of cases. We will use negative binomial regression again. The regression this time will be split in to two parts, one before the intervention (or introduction of new strain here) and one after (the pre and post-periods). This allows us to calculate an incidence rate ratio comparing the two time periods. Explaining the equation might make this clearer (if not then just ignore!).\nThe negative binomial regression can be defined as follows:\n\\[\\log(Y_t)= β_0 + β_1 \\times t+ β_2 \\times δ(t-t_0) + β_3\\times(t-t_0 )^+ + log(pop_t) + e_t\\]\nWhere: \\(Y_t\\)is the number of cases observed at time \\(t\\)\n\\(pop_t\\) is the population size in 100,000s at time \\(t\\) (not used here)\n\\(t_0\\) is the last year of the of the pre-period (including transition time if any)\n\\(δ(x\\) is the indicator function (it is 0 if x≤0 and 1 if x>0)\n\\((x)^+\\) is the cut off operator (it is x if x>0 and 0 otherwise)\n\\(e_t\\) denotes the residual Additional terms trend and season can be added as needed.\n\\(β_2 \\times δ(t-t_0) + β_3\\times(t-t_0 )^+\\) is the generalised linear part of the post-period and is zero in the pre-period. This means that the \\(β_2\\) and \\(β_3\\) estimates are the effects of the intervention.\nWe need to re-calculate the fourier terms without forecasting here, as we will use all the data available to us (i.e. retrospectively). Additionally we need to calculate the extra terms needed for the regression.\n\n## add in fourier terms using the epiweek and case_int variabless\ncounts$fourier <- select(counts, epiweek, case_int) %>% \n as_tsibble(index = epiweek) %>% \n fourier(K = 1)\n\n## define intervention week \nintervention_week <- yearweek(\"2008-12-31\")\n\n## define variables for regression \ncounts <- counts %>% \n mutate(\n ## corresponds to t in the formula\n ## count of weeks (could probably also just use straight epiweeks var)\n # linear = row_number(epiweek), \n ## corresponds to delta(t-t0) in the formula\n ## pre or post intervention period\n intervention = as.numeric(epiweek >= intervention_week), \n ## corresponds to (t-t0)^+ in the formula\n ## count of weeks post intervention\n ## (choose the larger number between 0 and whatever comes from calculation)\n time_post = pmax(0, epiweek - intervention_week + 1))\n\nWe then use these terms to fit a negative binomial regression, and produce a table with percentage change. What this example shows is that there was no significant change.\nCAUTION: Note the use of simulate_pi = FALSE within the predict() argument. This is because the default behaviour of trending is to use the ciTools package to estimate a prediction interval. This does not work if there are NA counts, and also produces more granular intervals. See ?trending::predict.trending_model_fit for details. \n\n## define the model you want to fit (negative binomial) \nmodel <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the furier terms to account for seasonality\n fourier + \n ## add in whether in the pre- or post-period \n intervention + \n ## add in the time post intervention \n time_post\n )\n\n## fit your model using the counts dataset\nfitted_model <- trending::fit(model, counts)\n\n## calculate confidence intervals and prediction intervals \nobserved <- predict(fitted_model, simulate_pi = FALSE)\n\n\n## show estimates and percentage change in a table\nfitted_model %>% \n ## extract original negative binomial regression\n get_model() %>% \n ## get a tidy dataframe of results\n tidy(exponentiate = TRUE, \n conf.int = TRUE) %>% \n ## only keep the intervention value \n filter(term == \"intervention\") %>% \n ## change the IRR to percentage change for estimate and CIs \n mutate(\n ## for each of the columns of interest - create a new column\n across(\n all_of(c(\"estimate\", \"conf.low\", \"conf.high\")), \n ## apply the formula to calculate percentage change\n .f = function(i) 100 * (i - 1), \n ## add a suffix to new column names with \"_perc\"\n .names = \"{.col}_perc\")\n ) %>% \n ## only keep (and rename) certain columns \n select(\"IRR\" = estimate, \n \"95%CI low\" = conf.low, \n \"95%CI high\" = conf.high,\n \"Percentage change\" = estimate_perc, \n \"95%CI low (perc)\" = conf.low_perc, \n \"95%CI high (perc)\" = conf.high_perc,\n \"p-value\" = p.value)\n\nAs previously we can visualise the outputs of the regression.\n\nestimate_res <- data.frame(observed$result)\n\nggplot(estimate_res, aes(x = epiweek)) + \n ## add in observed case counts as a line\n geom_line(aes(y = case_int, colour = \"Observed\")) + \n ## add in a line for the model estimate\n geom_line(aes(y = estimate, col = \"Estimate\")) + \n ## add in a band for the prediction intervals \n geom_ribbon(aes(ymin = lower_pi, \n ymax = upper_pi), \n alpha = 0.25) + \n ## add vertical line and label to show where forecasting started\n geom_vline(\n xintercept = as.Date(intervention_week), \n linetype = \"dashed\") + \n annotate(geom = \"text\", \n label = \"Intervention\", \n x = intervention_week, \n y = max(observed$upper_pi), \n angle = 90, \n vjust = 1\n ) + \n ## define colours\n scale_colour_manual(values = c(\"Observed\" = \"black\", \n \"Estimate\" = \"red\")) + \n ## make a traditional plot (with black axes and white background)\n theme_classic()\n\nWarning: Unknown or uninitialised column: `upper_pi`.\n\n\nWarning in max(observed$upper_pi): no non-missing arguments to max; returning\n-Inf", + "text": "23.8 Interrupted timeseries\nInterrupted timeseries (also called segmented regression or intervention analysis), is often used in assessing the impact of vaccines on the incidence of disease. But it can be used for assessing impact of a wide range of interventions or introductions. For example changes in hospital procedures or the introduction of a new disease strain to a population. In this example we will pretend that a new strain of Campylobacter was introduced to Germany at the end of 2008, and see if that affects the number of cases. We will use negative binomial regression again. The regression this time will be split in to two parts, one before the intervention (or introduction of new strain here) and one after (the pre and post-periods). This allows us to calculate an incidence rate ratio comparing the two time periods. Explaining the equation might make this clearer (if not then just ignore!).\nThe negative binomial regression can be defined as follows:\n\\[\\log(Y_t)= β_0 + β_1 \\times t+ β_2 \\times δ(t-t_0) + β_3\\times(t-t_0 )^+ + log(pop_t) + e_t\\]\nWhere: \\(Y_t\\)is the number of cases observed at time \\(t\\)\n\\(pop_t\\) is the population size in 100,000s at time \\(t\\) (not used here)\n\\(t_0\\) is the last year of the of the pre-period (including transition time if any)\n\\(δ(x\\) is the indicator function (it is 0 if x≤0 and 1 if x>0)\n\\((x)^+\\) is the cut off operator (it is x if x>0 and 0 otherwise)\n\\(e_t\\) denotes the residual Additional terms trend and season can be added as needed.\n\\(β_2 \\times δ(t-t_0) + β_3\\times(t-t_0 )^+\\) is the generalised linear part of the post-period and is zero in the pre-period. This means that the \\(β_2\\) and \\(β_3\\) estimates are the effects of the intervention.\nWe need to re-calculate the fourier terms without forecasting here, as we will use all the data available to us (i.e. retrospectively). Additionally we need to calculate the extra terms needed for the regression.\n\n## add in fourier terms using the epiweek and case_int variabless\ncounts$fourier <- select(counts, epiweek, case_int) %>% \n as_tsibble(index = epiweek) %>% \n fourier(K = 1)\n\n## define intervention week \nintervention_week <- yearweek(\"2008-12-31\")\n\n## define variables for regression \ncounts <- counts %>% \n mutate(\n ## corresponds to t in the formula\n ## count of weeks (could probably also just use straight epiweeks var)\n # linear = row_number(epiweek), \n ## corresponds to delta(t-t0) in the formula\n ## pre or post intervention period\n intervention = as.numeric(epiweek >= intervention_week), \n ## corresponds to (t-t0)^+ in the formula\n ## count of weeks post intervention\n ## (choose the larger number between 0 and whatever comes from calculation)\n time_post = pmax(0, epiweek - intervention_week + 1))\n\nWe then use these terms to fit a negative binomial regression, and produce a table with percentage change. What this example shows is that there was no significant change.\nCAUTION: Note the use of simulate_pi = FALSE within the predict() argument. This is because the default behaviour of trending is to use the ciTools package to estimate a prediction interval. This does not work if there are NA counts, and also produces more granular intervals. See ?trending::predict.trending_model_fit for details. \n\n## define the model you want to fit (negative binomial) \nmodel <- glm_nb_model(\n ## set number of cases as outcome of interest\n case_int ~\n ## use epiweek to account for the trend\n epiweek +\n ## use the furier terms to account for seasonality\n fourier + \n ## add in whether in the pre- or post-period \n intervention + \n ## add in the time post intervention \n time_post\n )\n\n## fit your model using the counts dataset\nfitted_model <- trending::fit(model, counts)\n\n## calculate confidence intervals and prediction intervals \nobserved <- predict(fitted_model, simulate_pi = FALSE)\n\n\n## extract original negative binomial regression\nfitted_model$result[[1]] %>%\n ## get a tidy dataframe of results\n tidy(exponentiate = TRUE, \n conf.int = TRUE) %>% \n ## only keep the intervention value \n filter(term == \"intervention\") %>% \n ## change the IRR to percentage change for estimate and CIs \n mutate(\n ## for each of the columns of interest - create a new column\n across(\n all_of(c(\"estimate\", \"conf.low\", \"conf.high\")), \n ## apply the formula to calculate percentage change\n .f = function(i) 100 * (i - 1), \n ## add a suffix to new column names with \"_perc\"\n .names = \"{.col}_perc\")\n ) %>% \n ## only keep (and rename) certain columns \n select(\"IRR\" = estimate, \n \"95%CI low\" = conf.low, \n \"95%CI high\" = conf.high,\n \"Percentage change\" = estimate_perc, \n \"95%CI low (perc)\" = conf.low_perc, \n \"95%CI high (perc)\" = conf.high_perc,\n \"p-value\" = p.value)\n\nAs previously we can visualise the outputs of the regression.\n\nestimate_res <- data.frame(observed$result)\n\nggplot(estimate_res, aes(x = epiweek)) + \n ## add in observed case counts as a line\n geom_line(aes(y = case_int, colour = \"Observed\")) + \n ## add in a line for the model estimate\n geom_line(aes(y = estimate, col = \"Estimate\")) + \n ## add in a band for the prediction intervals \n geom_ribbon(aes(ymin = lower_pi, \n ymax = upper_pi), \n alpha = 0.25) + \n ## add vertical line and label to show where forecasting started\n geom_vline(\n xintercept = as.Date(intervention_week), \n linetype = \"dashed\") + \n annotate(geom = \"text\", \n label = \"Intervention\", \n x = intervention_week, \n y = max(observed$upper_pi), \n angle = 90, \n vjust = 1\n ) + \n ## define colours\n scale_colour_manual(values = c(\"Observed\" = \"black\", \n \"Estimate\" = \"red\")) + \n ## make a traditional plot (with black axes and white background)\n theme_classic()\n\nWarning: Unknown or uninitialised column: `upper_pi`.\n\n\nWarning in max(observed$upper_pi): no non-missing arguments to max; returning\n-Inf", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -2067,7 +2067,7 @@ "href": "new_pages/time_series.html#resources", "title": "23  Time series and outbreak detection", "section": "23.9 Resources", - "text": "23.9 Resources\nforecasting: principles and practice textbook\nEPIET timeseries analysis case studies\nPenn State course Surveillance package manuscript", + "text": "23.9 Resources\nforecasting: principles and practice textbook\nEPIET timeseries analysis case studies\nPenn State course\nSurveillance package manuscript", "crumbs": [ "Analysis", "23  Time series and outbreak detection" @@ -2089,7 +2089,7 @@ "href": "new_pages/epidemic_models.html#overview", "title": "24  Epidemic modeling", "section": "", - "text": "estimate the effective reproduction number Rt and related statistics such as the doubling time\nproduce short-term projections of future incidence", + "text": "Estimate the effective reproduction number Rt and related statistics such as the doubling time.\nProduce short-term projections of future incidence.", "crumbs": [ "Analysis", "24  Epidemic modeling" @@ -2111,7 +2111,7 @@ "href": "new_pages/epidemic_models.html#estimating-rt", "title": "24  Epidemic modeling", "section": "24.3 Estimating Rt", - "text": "24.3 Estimating Rt\n\nEpiNow2 vs. EpiEstim\nThe reproduction number R is a measure of the transmissibility of a disease and is defined as the expected number of secondary cases per infected case. In a fully susceptible population, this value represents the basic reproduction number R0. However, as the number of susceptible individuals in a population changes over the course of an outbreak or pandemic, and as various response measures are implemented, the most commonly used measure of transmissibility is the effective reproduction number Rt; this is defined as the expected number of secondary cases per infected case at a given time t.\nThe EpiNow2 package provides the most sophisticated framework for estimating Rt. It has two key advantages over the other commonly used package, EpiEstim:\n\nIt accounts for delays in reporting and can therefore estimate Rt even when recent data is incomplete.\nIt estimates Rt on dates of infection rather than the dates of onset of reporting, which means that the effect of an intervention will be immediately reflected in a change in Rt, rather than with a delay.\n\nHowever, it also has two key disadvantages:\n\nIt requires knowledge of the generation time distribution (i.e. distribution of delays between infection of a primary and secondary cases), incubation period distribution (i.e. distribution of delays between infection and symptom onset) and any further delay distribution relevant to your data (e.g. if you have dates of reporting, you require the distribution of delays from symptom onset to reporting). While this will allow more accurate estimation of Rt, EpiEstim only requires the serial interval distribution (i.e. the distribution of delays between symptom onset of a primary and a secondary case), which may be the only distribution available to you.\nEpiNow2 is significantly slower than EpiEstim, anecdotally by a factor of about 100-1000! For example, estimating Rt for the sample outbreak considered in this section takes about four hours (this was run for a large number of iterations to ensure high accuracy and could probably be reduced if necessary, however the points stands that the algorithm is slow in general). This may be unfeasible if you are regularly updating your Rt estimates.\n\nWhich package you choose to use will therefore depend on the data, time and computational resources available to you.\n\n\nEpiNow2\n\nEstimating delay distributions\nThe delay distributions required to run EpiNow2 depend on the data you have. Essentially, you need to be able to describe the delay from the date of infection to the date of the event you want to use to estimate Rt. If you are using dates of onset, this would simply be the incubation period distribution. If you are using dates of reporting, you require the delay from infection to reporting. As this distribution is unlikely to be known directly, EpiNow2 lets you chain multiple delay distributions together; in this case, the delay from infection to symptom onset (e.g. the incubation period, which is likely known) and from symptom onset to reporting (which you can often estimate from the data).\nAs we have the dates of onset for all our cases in the example linelist, we will only require the incubation period distribution to link our data (e.g. dates of symptom onset) to the date of infection. We can either estimate this distribution from the data or use values from the literature.\nA literature estimate of the incubation period of Ebola (taken from this paper) with a mean of 9.1, standard deviation of 7.3 and maximum value of 30 would be specified as follows:\n\nincubation_period_lit <- list(\n mean = log(9.1),\n mean_sd = log(0.1),\n sd = log(7.3),\n sd_sd = log(0.1),\n max = 30\n)\n\nNote that EpiNow2 requires these delay distributions to be provided on a log scale, hence the log call around each value (except the max parameter which, confusingly, has to be provided on a natural scale). The mean_sd and sd_sd define the standard deviation of the mean and standard deviation estimates. As these are not known in this case, we choose the fairly arbitrary value of 0.1.\nIn this analysis, we instead estimate the incubation period distribution from the linelist itself using the function bootstrapped_dist_fit, which will fit a lognormal distribution to the observed delays between infection and onset in the linelist.\n\n## estimate incubation period\nincubation_period <- bootstrapped_dist_fit(\n linelist$date_onset - linelist$date_infection,\n dist = \"lognormal\",\n max_value = 100,\n bootstraps = 1\n)\n\nThe other distribution we require is the generation time. As we have data on infection times and transmission links, we can estimate this distribution from the linelist by calculating the delay between infection times of infector-infectee pairs. To do this, we use the handy get_pairwise function from the package epicontacts, which allows us to calculate pairwise differences of linelist properties between transmission pairs. We first create an epicontacts object (see Transmission chains page for further details):\n\n## generate contacts\ncontacts <- linelist %>%\n transmute(\n from = infector,\n to = case_id\n ) %>%\n drop_na()\n\n## generate epicontacts object\nepic <- make_epicontacts(\n linelist = linelist,\n contacts = contacts, \n directed = TRUE\n)\n\nWe then fit the difference in infection times between transmission pairs, calculated using get_pairwise, to a gamma distribution:\n\n## estimate gamma generation time\ngeneration_time <- bootstrapped_dist_fit(\n get_pairwise(epic, \"date_infection\"),\n dist = \"gamma\",\n max_value = 20,\n bootstraps = 1\n)\n\n\n\nRunning EpiNow2\nNow we just need to calculate daily incidence from the linelist, which we can do easily with the dplyr functions group_by() and n(). Note that EpiNow2 requires the column names to be date and confirm.\n\n## get incidence from onset dates\ncases <- linelist %>%\n group_by(date = date_onset) %>%\n summarise(confirm = n())\n\nWe can then estimate Rt using the epinow function. Some notes on the inputs:\n\nWe can provide any number of ‘chained’ delay distributions to the delays argument; we would simply insert them alongside the incubation_period object within the delay_opts function.\nreturn_output ensures the output is returned within R and not just saved to a file.\nverbose specifies that we want a readout of the progress.\nhorizon indicates how many days we want to project future incidence for.\nWe pass additional options to the stan argument to specify how long we want to run the inference for. Increasing samples and chains will give you a more accurate estimate that better characterises uncertainty, however will take longer to run.\n\n\n## run epinow\nepinow_res <- epinow(\n reported_cases = cases,\n generation_time = generation_time,\n delays = delay_opts(incubation_period),\n return_output = TRUE,\n verbose = TRUE,\n horizon = 21,\n stan = stan_opts(samples = 750, chains = 4)\n)\n\n\n\nAnalysing outputs\nOnce the code has finished running, we can plot a summary very easily as follows. Scroll the image to see the full extent.\n\n## plot summary figure\nplot(epinow_res)\n\n\n\n\n\n\n\n\nWe can also look at various summary statistics:\n\n## summary table\nepinow_res$summary\n\n measure estimate\n <char> <char>\n1: New confirmed cases by infection date 4 (2 -- 6)\n2: Expected change in daily cases Unsure\n3: Effective reproduction no. 0.88 (0.73 -- 1.1)\n4: Rate of growth -0.012 (-0.028 -- 0.0052)\n5: Doubling/halving time (days) -60 (130 -- -25)\n numeric_estimate\n <list>\n1: <data.table[1x9]>\n2: 0.56\n3: <data.table[1x9]>\n4: <data.table[1x9]>\n5: <data.table[1x9]>\n\n\nFor further analyses and custom plotting, you can access the summarised daily estimates via $estimates$summarised. We will convert this from the default data.table to a tibble for ease of use with dplyr.\n\n## extract summary and convert to tibble\nestimates <- as_tibble(epinow_res$estimates$summarised)\nestimates\n\n\n\n\n\n\n\nAs an example, let’s make a plot of the doubling time and Rt. We will only look at the first few months of the outbreak when Rt is well above one, to avoid plotting extremely high doublings times.\nWe use the formula log(2)/growth_rate to calculate the doubling time from the estimated growth rate.\n\n## make wide df for median plotting\ndf_wide <- estimates %>%\n filter(\n variable %in% c(\"growth_rate\", \"R\"),\n date < as.Date(\"2014-09-01\")\n ) %>%\n ## convert growth rates to doubling times\n mutate(\n across(\n c(median, lower_90:upper_90),\n ~ case_when(\n variable == \"growth_rate\" ~ log(2)/.x,\n TRUE ~ .x\n )\n ),\n ## rename variable to reflect transformation\n variable = replace(variable, variable == \"growth_rate\", \"doubling_time\")\n )\n\n## make long df for quantile plotting\ndf_long <- df_wide %>%\n ## here we match matching quantiles (e.g. lower_90 to upper_90)\n pivot_longer(\n lower_90:upper_90,\n names_to = c(\".value\", \"quantile\"),\n names_pattern = \"(.+)_(.+)\"\n )\n\n## make plot\nggplot() +\n geom_ribbon(\n data = df_long,\n aes(x = date, ymin = lower, ymax = upper, alpha = quantile),\n color = NA\n ) +\n geom_line(\n data = df_wide,\n aes(x = date, y = median)\n ) +\n ## use label_parsed to allow subscript label\n facet_wrap(\n ~ variable,\n ncol = 1,\n scales = \"free_y\",\n labeller = as_labeller(c(R = \"R[t]\", doubling_time = \"Doubling~time\"), label_parsed),\n strip.position = 'left'\n ) +\n ## manually define quantile transparency\n scale_alpha_manual(\n values = c(`20` = 0.7, `50` = 0.4, `90` = 0.2),\n labels = function(x) paste0(x, \"%\")\n ) +\n labs(\n x = NULL,\n y = NULL,\n alpha = \"Credibel\\ninterval\"\n ) +\n scale_x_date(\n date_breaks = \"1 month\",\n date_labels = \"%b %d\\n%Y\"\n ) +\n theme_minimal(base_size = 14) +\n theme(\n strip.background = element_blank(),\n strip.placement = 'outside'\n )\n\n\n\n\n\n\n\n\n\n\n\n\nEpiEstim\nTo run EpiEstim, we need to provide data on daily incidence and specify the serial interval (i.e. the distribution of delays between symptom onset of primary and secondary cases).\nIncidence data can be provided to EpiEstim as a vector, a data frame, or an incidence object from the original incidence package. You can even distinguish between imports and locally acquired infections; see the documentation at ?estimate_R for further details.\nWe will create the input using incidence2. See the page on Epidemic curves for more examples with the incidence2 package. Since there have been updates to the incidence2 package that don’t completely align with estimateR()’s expected input, there are some minor additional steps needed. The incidence object consists of a tibble with dates and their respective case counts. We use complete() from tidyr to ensure all dates are included (even those with no cases), and then rename() the columns to align with what is expected by estimate_R() in a later step.\n\n## get incidence from onset date\ncases <- incidence2::incidence(linelist, date_index = \"date_onset\") %>% # get case counts by day\n tidyr::complete(date_index = seq.Date( # ensure all dates are represented\n from = min(date_index, na.rm = T),\n to = max(date_index, na.rm=T),\n by = \"day\"),\n fill = list(count = 0)) %>% # convert NA counts to 0\n rename(I = count, # rename to names expected by estimateR\n dates = date_index)\n\nThe package provides several options for specifying the serial interval, the details of which are provided in the documentation at ?estimate_R. We will cover two of them here.\n\nUsing serial interval estimates from the literature\nUsing the option method = \"parametric_si\", we can manually specify the mean and standard deviation of the serial interval in a config object created using the function make_config. We use a mean and standard deviation of 12.0 and 5.2, respectively, defined in this paper:\n\n## make config\nconfig_lit <- make_config(\n mean_si = 12.0,\n std_si = 5.2\n)\n\nWe can then estimate Rt with the estimate_R function:\n\ncases <- cases %>% \n filter(!is.na(date))\n\n\n#create a dataframe for the function estimate_R()\ncases_incidence <- data.frame(dates = seq.Date(from = min(cases$dates),\n to = max(cases$dates), \n by = 1))\n\ncases_incidence <- left_join(cases_incidence, cases) %>% \n select(dates, I) %>% \n mutate(I = ifelse(is.na(I), 0, I))\n\nJoining with `by = join_by(dates)`\n\nepiestim_res_lit <- estimate_R(\n incid = cases_incidence,\n method = \"parametric_si\",\n config = config_lit\n)\n\nDefault config will estimate R on weekly sliding windows.\n To change this change the t_start and t_end arguments. \n\n\nand plot a summary of the outputs:\n\nplot(epiestim_res_lit)\n\n\n\n\n\n\n\n\n\n\nUsing serial interval estimates from the data\nAs we have data on dates of symptom onset and transmission links, we can also estimate the serial interval from the linelist by calculating the delay between onset dates of infector-infectee pairs. As we did in the EpiNow2 section, we will use the get_pairwise function from the epicontacts package, which allows us to calculate pairwise differences of linelist properties between transmission pairs. We first create an epicontacts object (see Transmission chains page for further details):\n\n## generate contacts\ncontacts <- linelist %>%\n transmute(\n from = infector,\n to = case_id\n ) %>%\n drop_na()\n\n## generate epicontacts object\nepic <- make_epicontacts(\n linelist = linelist,\n contacts = contacts, \n directed = TRUE\n)\n\nWe then fit the difference in onset dates between transmission pairs, calculated using get_pairwise, to a gamma distribution. We use the handy fit_disc_gamma from the epitrix package for this fitting procedure, as we require a discretised distribution.\n\n## estimate gamma serial interval\nserial_interval <- fit_disc_gamma(get_pairwise(epic, \"date_onset\"))\n\nWe then pass this information to the config object, run EpiEstim again and plot the results:\n\n## make config\nconfig_emp <- make_config(\n mean_si = serial_interval$mu,\n std_si = serial_interval$sd\n)\n\n## run epiestim\nepiestim_res_emp <- estimate_R(\n incid = cases_incidence,\n method = \"parametric_si\",\n config = config_emp\n)\n\nDefault config will estimate R on weekly sliding windows.\n To change this change the t_start and t_end arguments. \n\n## plot outputs\nplot(epiestim_res_emp)\n\n\n\n\n\n\n\n\n\n\nSpecifying estimation time windows\nThese default options will provide a weekly sliding estimate and might act as a warning that you are estimating Rt too early in the outbreak for a precise estimate. You can change this by setting a later start date for the estimation as shown below. Unfortunately, EpiEstim only provides a very clunky way of specifying these estimations times, in that you have to provide a vector of integers referring to the start and end dates for each time window.\n\n## define a vector of dates starting on June 1st\nstart_dates <- seq.Date(\n as.Date(\"2014-06-01\"),\n max(cases$dates) - 7,\n by = 1\n) %>%\n ## subtract the starting date to convert to numeric\n `-`(min(cases$dates)) %>%\n ## convert to integer\n as.integer()\n\n## add six days for a one week sliding window\nend_dates <- start_dates + 6\n \n## make config\nconfig_partial <- make_config(\n mean_si = 12.0,\n std_si = 5.2,\n t_start = start_dates,\n t_end = end_dates\n)\n\nNow we re-run EpiEstim and can see that the estimates only start from June:\n\n## run epiestim\nepiestim_res_partial <- estimate_R(\n incid = cases_incidence,\n method = \"parametric_si\",\n config = config_partial\n)\n\n## plot outputs\nplot(epiestim_res_partial)\n\n\n\n\n\n\n\n\n\n\nAnalysing outputs\nThe main outputs can be accessed via $R. As an example, we will create a plot of Rt and a measure of “transmission potential” given by the product of Rt and the number of cases reported on that day; this represents the expected number of cases in the next generation of infection.\n\n## make wide dataframe for median\ndf_wide <- epiestim_res_lit$R %>%\n rename_all(clean_labels) %>%\n rename(\n lower_95_r = quantile_0_025_r,\n lower_90_r = quantile_0_05_r,\n lower_50_r = quantile_0_25_r,\n upper_50_r = quantile_0_75_r,\n upper_90_r = quantile_0_95_r,\n upper_95_r = quantile_0_975_r,\n ) %>%\n mutate(\n ## extract the median date from t_start and t_end\n dates = epiestim_res_emp$dates[round(map2_dbl(t_start, t_end, median))],\n var = \"R[t]\"\n ) %>%\n ## merge in daily incidence data\n left_join(cases, \"dates\") %>%\n ## calculate risk across all r estimates\n mutate(\n across(\n lower_95_r:upper_95_r,\n ~ .x*I,\n .names = \"{str_replace(.col, '_r', '_risk')}\"\n )\n ) %>%\n ## seperate r estimates and risk estimates\n pivot_longer(\n contains(\"median\"),\n names_to = c(\".value\", \"variable\"),\n names_pattern = \"(.+)_(.+)\"\n ) %>%\n ## assign factor levels\n mutate(variable = factor(variable, c(\"risk\", \"r\")))\n\n## make long dataframe from quantiles\ndf_long <- df_wide %>%\n select(-variable, -median) %>%\n ## seperate r/risk estimates and quantile levels\n pivot_longer(\n contains(c(\"lower\", \"upper\")),\n names_to = c(\".value\", \"quantile\", \"variable\"),\n names_pattern = \"(.+)_(.+)_(.+)\"\n ) %>%\n mutate(variable = factor(variable, c(\"risk\", \"r\")))\n\n## make plot\nggplot() +\n geom_ribbon(\n data = df_long,\n aes(x = dates, ymin = lower, ymax = upper, alpha = quantile),\n color = NA\n ) +\n geom_line(\n data = df_wide,\n aes(x = dates, y = median),\n alpha = 0.2\n ) +\n ## use label_parsed to allow subscript label\n facet_wrap(\n ~ variable,\n ncol = 1,\n scales = \"free_y\",\n labeller = as_labeller(c(r = \"R[t]\", risk = \"Transmission~potential\"), label_parsed),\n strip.position = 'left'\n ) +\n ## manually define quantile transparency\n scale_alpha_manual(\n values = c(`50` = 0.7, `90` = 0.4, `95` = 0.2),\n labels = function(x) paste0(x, \"%\")\n ) +\n labs(\n x = NULL,\n y = NULL,\n alpha = \"Credible\\ninterval\"\n ) +\n scale_x_date(\n date_breaks = \"1 month\",\n date_labels = \"%b %d\\n%Y\"\n ) +\n theme_minimal(base_size = 14) +\n theme(\n strip.background = element_blank(),\n strip.placement = 'outside'\n )", + "text": "24.3 Estimating Rt\n\nEpiNow2 vs. EpiEstim\nThe reproduction number R is a measure of the transmissibility of a disease and is defined as the expected number of secondary cases per infected case. In a fully susceptible population, this value represents the basic reproduction number R0. However, as the number of susceptible individuals in a population changes over the course of an outbreak or pandemic, and as various response measures are implemented, the most commonly used measure of transmissibility is the effective reproduction number Rt; this is defined as the expected number of secondary cases per infected case at a given time t.\nThe EpiNow2 package provides the most sophisticated framework for estimating Rt. It has two key advantages over the other commonly used package, EpiEstim:\n\nIt accounts for delays in reporting and can therefore estimate Rt even when recent data is incomplete.\nIt estimates Rt on dates of infection rather than the dates of onset of reporting, which means that the effect of an intervention will be immediately reflected in a change in Rt, rather than with a delay.\n\nHowever, it also has two key disadvantages:\n\nIt requires knowledge of the generation time distribution (i.e. distribution of delays between infection of a primary and secondary cases), incubation period distribution (i.e. distribution of delays between infection and symptom onset) and any further delay distribution relevant to your data (e.g. if you have dates of reporting, you require the distribution of delays from symptom onset to reporting). While this will allow more accurate estimation of Rt, EpiEstim only requires the serial interval distribution (i.e. the distribution of delays between symptom onset of a primary and a secondary case), which may be the only distribution available to you.\nEpiNow2 is significantly slower than EpiEstim, anecdotally by a factor of about 100-1000! For example, estimating Rt for the sample outbreak considered in this section takes about four hours (this was run for a large number of iterations to ensure high accuracy and could probably be reduced if necessary, however the points stands that the algorithm is slow in general). This may be unfeasible if you are regularly updating your Rt estimates.\n\nWhich package you choose to use will therefore depend on the data, time and computational resources available to you.\n\n\nEpiNow2\n\nEstimating delay distributions\nThe delay distributions required to run EpiNow2 depend on the data you have. Essentially, you need to be able to describe the delay from the date of infection to the date of the event you want to use to estimate Rt. If you are using dates of onset, this would simply be the incubation period distribution. If you are using dates of reporting, you require the delay from infection to reporting. As this distribution is unlikely to be known directly, EpiNow2 lets you chain multiple delay distributions together; in this case, the delay from infection to symptom onset (e.g. the incubation period, which is likely known) and from symptom onset to reporting (which you can often estimate from the data).\nAs we have the dates of onset for all our cases in the example linelist, we will only require the incubation period distribution to link our data (e.g. dates of symptom onset) to the date of infection. We can either estimate this distribution from the data or use values from the literature.\nA literature estimate of the incubation period of Ebola (taken from this paper) with a mean of 9.1, standard deviation of 7.3 and maximum value of 30 would be specified as follows:\n\nincubation_period_lit <- list(\n mean = log(9.1),\n mean_sd = log(0.1),\n sd = log(7.3),\n sd_sd = log(0.1),\n max = 30\n)\n\nNote that EpiNow2 requires these delay distributions to be provided on a log scale, hence the log call around each value (except the max parameter which, confusingly, has to be provided on a natural scale). The mean_sd and sd_sd define the standard deviation of the mean and standard deviation estimates. As these are not known in this case, we choose the fairly arbitrary value of 0.1.\nIn this analysis, we instead estimate the incubation period distribution from the linelist itself using the function bootstrapped_dist_fit, which will fit a lognormal distribution to the observed delays between infection and onset in the linelist.\n\n## estimate incubation period\nincubation_period <- bootstrapped_dist_fit(\n linelist$date_onset - linelist$date_infection,\n dist = \"lognormal\",\n max_value = 100,\n bootstraps = 1\n)\n\nThe other distribution we require is the generation time. As we have data on infection times and transmission links, we can estimate this distribution from the linelist by calculating the delay between infection times of infector-infectee pairs. To do this, we use the handy get_pairwise function from the package epicontacts, which allows us to calculate pairwise differences of linelist properties between transmission pairs. We first create an epicontacts object (see Transmission chains page for further details):\n\n## generate contacts\ncontacts <- linelist %>%\n transmute(\n from = infector,\n to = case_id\n ) %>%\n drop_na()\n\n## generate epicontacts object\nepic <- make_epicontacts(\n linelist = linelist,\n contacts = contacts, \n directed = TRUE\n)\n\nWe then fit the difference in infection times between transmission pairs, calculated using get_pairwise, to a gamma distribution:\n\n## estimate gamma generation time\ngeneration_time <- bootstrapped_dist_fit(\n get_pairwise(epic, \"date_infection\"),\n dist = \"gamma\",\n max_value = 20,\n bootstraps = 1\n)\n\n\n\nRunning EpiNow2\nNow we just need to calculate daily incidence from the linelist, which we can do easily with the dplyr functions group_by() and n(). Note that EpiNow2 requires the column names to be date and confirm.\n\n## get incidence from onset dates\ncases <- linelist %>%\n group_by(date = date_onset) %>%\n summarise(confirm = n()) %>%\n drop_na() #epinow wont run with NA values\n\nWe can then estimate Rt using the epinow function. Some notes on the inputs:\n\nWe can provide any number of ‘chained’ delay distributions to the delays argument; we would simply insert them alongside the incubation_period object within the delay_opts function.\nreturn_output ensures the output is returned within R and not just saved to a file.\nverbose specifies that we want a readout of the progress.\nhorizon indicates how many days we want to project future incidence for.\nWe pass additional options to the stan argument to specify how long we want to run the inference for. Increasing samples and chains will give you a more accurate estimate that better characterises uncertainty, however will take longer to run.\n\n\n## run epinow\nepinow_res <- epinow(\n reported_cases = cases,\n generation_time = generation_time_opts(generation_time),\n delays = delay_opts(incubation_period),\n return_output = TRUE,\n verbose = TRUE,\n horizon = 21,\n stan = stan_opts(samples = 750, chains = 4)\n)\n\n\n\nAnalysing outputs\nOnce the code has finished running, we can plot a summary very easily as follows. Scroll the image to see the full extent.\n\n## plot summary figure\nplot(epinow_res)\n\n\n\n\n\n\n\n\nWe can also look at various summary statistics:\n\n## summary table\nepinow_res$summary\n\n measure estimate\n <char> <char>\n1: New confirmed cases by infection date 4 (2 -- 6)\n2: Expected change in daily cases Unsure\n3: Effective reproduction no. 0.88 (0.73 -- 1.1)\n4: Rate of growth -0.012 (-0.028 -- 0.0052)\n5: Doubling/halving time (days) -60 (130 -- -25)\n numeric_estimate\n <list>\n1: <data.table[1x9]>\n2: 0.56\n3: <data.table[1x9]>\n4: <data.table[1x9]>\n5: <data.table[1x9]>\n\n\nFor further analyses and custom plotting, you can access the summarised daily estimates via $estimates$summarised. We will convert this from the default data.table to a tibble for ease of use with dplyr.\n\n## extract summary and convert to tibble\nestimates <- as_tibble(epinow_res$estimates$summarised)\nestimates\n\n\n\n\n\n\n\nAs an example, let’s make a plot of the doubling time and Rt. We will only look at the first few months of the outbreak when Rt is well above one, to avoid plotting extremely high doublings times.\nWe use the formula log(2)/growth_rate to calculate the doubling time from the estimated growth rate.\n\n## make wide df for median plotting\ndf_wide <- estimates %>%\n filter(\n variable %in% c(\"growth_rate\", \"R\"),\n date < as.Date(\"2014-09-01\")\n ) %>%\n ## convert growth rates to doubling times\n mutate(\n across(\n c(median, lower_90:upper_90),\n ~ case_when(\n variable == \"growth_rate\" ~ log(2)/.x,\n TRUE ~ .x\n )\n ),\n ## rename variable to reflect transformation\n variable = replace(variable, variable == \"growth_rate\", \"doubling_time\")\n )\n\n## make long df for quantile plotting\ndf_long <- df_wide %>%\n ## here we match matching quantiles (e.g. lower_90 to upper_90)\n pivot_longer(\n lower_90:upper_90,\n names_to = c(\".value\", \"quantile\"),\n names_pattern = \"(.+)_(.+)\"\n )\n\n## make plot\nggplot() +\n geom_ribbon(\n data = df_long,\n aes(x = date, ymin = lower, ymax = upper, alpha = quantile),\n color = NA\n ) +\n geom_line(\n data = df_wide,\n aes(x = date, y = median)\n ) +\n ## use label_parsed to allow subscript label\n facet_wrap(\n ~ variable,\n ncol = 1,\n scales = \"free_y\",\n labeller = as_labeller(c(R = \"R[t]\", doubling_time = \"Doubling~time\"), label_parsed),\n strip.position = 'left'\n ) +\n ## manually define quantile transparency\n scale_alpha_manual(\n values = c(`20` = 0.7, `50` = 0.4, `90` = 0.2),\n labels = function(x) paste0(x, \"%\")\n ) +\n labs(\n x = NULL,\n y = NULL,\n alpha = \"Credibel\\ninterval\"\n ) +\n scale_x_date(\n date_breaks = \"1 month\",\n date_labels = \"%b %d\\n%Y\"\n ) +\n theme_minimal(base_size = 14) +\n theme(\n strip.background = element_blank(),\n strip.placement = 'outside'\n )\n\n\n\n\n\n\n\n\n\n\n\n\nEpiEstim\nTo run EpiEstim, we need to provide data on daily incidence and specify the serial interval (i.e. the distribution of delays between symptom onset of primary and secondary cases).\nIncidence data can be provided to EpiEstim as a vector, a data frame, or an incidence object from the original incidence package. You can even distinguish between imports and locally acquired infections; see the documentation at ?estimate_R for further details.\nWe will create the input using incidence2. See the page on Epidemic curves for more examples with the incidence2 package. Since there have been updates to the incidence2 package that don’t completely align with estimateR()’s expected input, there are some minor additional steps needed. The incidence object consists of a tibble with dates and their respective case counts. We use complete() from tidyr to ensure all dates are included (even those with no cases), and then rename() the columns to align with what is expected by estimate_R() in a later step.\n\n## get incidence from onset date\ncases <- incidence2::incidence(linelist, date_index = \"date_onset\") %>% # get case counts by day\n tidyr::complete(date_index = seq.Date( # ensure all dates are represented\n from = min(date_index, na.rm = T),\n to = max(date_index, na.rm=T),\n by = \"day\"),\n fill = list(count = 0)) %>% # convert NA counts to 0\n rename(I = count, # rename to names expected by estimateR\n dates = date_index\n )\n\nThe package provides several options for specifying the serial interval, the details of which are provided in the documentation at ?estimate_R. We will cover two of them here.\n\nUsing serial interval estimates from the literature\nUsing the option method = \"parametric_si\", we can manually specify the mean and standard deviation of the serial interval in a config object created using the function make_config. We use a mean and standard deviation of 12.0 and 5.2, respectively, defined in this paper:\n\n## make config\nconfig_lit <- make_config(\n mean_si = 12.0,\n std_si = 5.2\n)\n\nWe can then estimate Rt with the estimate_R function:\n\ncases <- cases %>% \n filter(!is.na(date))\n\n\n#create a dataframe for the function estimate_R()\ncases_incidence <- data.frame(dates = seq.Date(from = min(cases$dates),\n to = max(cases$dates), \n by = 1))\n\ncases_incidence <- left_join(cases_incidence, cases) %>% \n select(dates, I) %>% \n mutate(I = ifelse(is.na(I), 0, I))\n\nJoining with `by = join_by(dates)`\n\nepiestim_res_lit <- estimate_R(\n incid = cases_incidence,\n method = \"parametric_si\",\n config = config_lit\n)\n\nDefault config will estimate R on weekly sliding windows.\n To change this change the t_start and t_end arguments. \n\n\nand plot a summary of the outputs:\n\nplot(epiestim_res_lit)\n\n\n\n\n\n\n\n\n\n\nUsing serial interval estimates from the data\nAs we have data on dates of symptom onset and transmission links, we can also estimate the serial interval from the linelist by calculating the delay between onset dates of infector-infectee pairs. As we did in the EpiNow2 section, we will use the get_pairwise function from the epicontacts package, which allows us to calculate pairwise differences of linelist properties between transmission pairs. We first create an epicontacts object (see Transmission chains page for further details):\n\n## generate contacts\ncontacts <- linelist %>%\n transmute(\n from = infector,\n to = case_id\n ) %>%\n drop_na()\n\n## generate epicontacts object\nepic <- make_epicontacts(\n linelist = linelist,\n contacts = contacts, \n directed = TRUE\n)\n\nWe then fit the difference in onset dates between transmission pairs, calculated using get_pairwise, to a gamma distribution. We use the handy fit_disc_gamma from the epitrix package for this fitting procedure, as we require a discretised distribution.\n\n## estimate gamma serial interval\nserial_interval <- fit_disc_gamma(get_pairwise(epic, \"date_onset\"))\n\nWe then pass this information to the config object, run EpiEstim again and plot the results:\n\n## make config\nconfig_emp <- make_config(\n mean_si = serial_interval$mu,\n std_si = serial_interval$sd\n)\n\n## run epiestim\nepiestim_res_emp <- estimate_R(\n incid = cases_incidence,\n method = \"parametric_si\",\n config = config_emp\n)\n\nDefault config will estimate R on weekly sliding windows.\n To change this change the t_start and t_end arguments. \n\n## plot outputs\nplot(epiestim_res_emp)\n\n\n\n\n\n\n\n\n\n\nSpecifying estimation time windows\nThese default options will provide a weekly sliding estimate and might act as a warning that you are estimating Rt too early in the outbreak for a precise estimate. You can change this by setting a later start date for the estimation as shown below. Unfortunately, EpiEstim only provides a very clunky way of specifying these estimations times, in that you have to provide a vector of integers referring to the start and end dates for each time window.\n\n## define a vector of dates starting on June 1st\nstart_dates <- seq.Date(\n as.Date(\"2014-06-01\"),\n max(cases$dates) - 7,\n by = 1\n) %>%\n ## subtract the starting date to convert to numeric\n `-`(min(cases$dates)) %>%\n ## convert to integer\n as.integer()\n\n## add six days for a one week sliding window\nend_dates <- start_dates + 6\n \n## make config\nconfig_partial <- make_config(\n mean_si = 12.0,\n std_si = 5.2,\n t_start = start_dates,\n t_end = end_dates\n)\n\nNow we re-run EpiEstim and can see that the estimates only start from June:\n\n## run epiestim\nepiestim_res_partial <- estimate_R(\n incid = cases_incidence,\n method = \"parametric_si\",\n config = config_partial\n)\n\n## plot outputs\nplot(epiestim_res_partial)\n\n\n\n\n\n\n\n\n\n\nAnalysing outputs\nThe main outputs can be accessed via $R. As an example, we will create a plot of Rt and a measure of “transmission potential” given by the product of Rt and the number of cases reported on that day; this represents the expected number of cases in the next generation of infection.\n\n## make wide dataframe for median\ndf_wide <- epiestim_res_lit$R %>%\n rename_all(clean_labels) %>%\n rename(\n lower_95_r = quantile_0_025_r,\n lower_90_r = quantile_0_05_r,\n lower_50_r = quantile_0_25_r,\n upper_50_r = quantile_0_75_r,\n upper_90_r = quantile_0_95_r,\n upper_95_r = quantile_0_975_r,\n ) %>%\n mutate(\n ## extract the median date from t_start and t_end\n dates = epiestim_res_emp$dates[round(map2_dbl(t_start, t_end, median))],\n var = \"R[t]\"\n ) %>%\n ## merge in daily incidence data\n left_join(cases, \"dates\") %>%\n ## calculate risk across all r estimates\n mutate(\n across(\n lower_95_r:upper_95_r,\n ~ .x*I,\n .names = \"{str_replace(.col, '_r', '_risk')}\"\n )\n ) %>%\n ## seperate r estimates and risk estimates\n pivot_longer(\n contains(\"median\"),\n names_to = c(\".value\", \"variable\"),\n names_pattern = \"(.+)_(.+)\"\n ) %>%\n ## assign factor levels\n mutate(variable = factor(variable, c(\"risk\", \"r\")))\n\n## make long dataframe from quantiles\ndf_long <- df_wide %>%\n select(-variable, -median) %>%\n ## seperate r/risk estimates and quantile levels\n pivot_longer(\n contains(c(\"lower\", \"upper\")),\n names_to = c(\".value\", \"quantile\", \"variable\"),\n names_pattern = \"(.+)_(.+)_(.+)\"\n ) %>%\n mutate(variable = factor(variable, c(\"risk\", \"r\")))\n\n## make plot\nggplot() +\n geom_ribbon(\n data = df_long,\n aes(x = dates, ymin = lower, ymax = upper, alpha = quantile),\n color = NA\n ) +\n geom_line(\n data = df_wide,\n aes(x = dates, y = median),\n alpha = 0.2\n ) +\n ## use label_parsed to allow subscript label\n facet_wrap(\n ~ variable,\n ncol = 1,\n scales = \"free_y\",\n labeller = as_labeller(c(r = \"R[t]\", risk = \"Transmission~potential\"), label_parsed),\n strip.position = 'left'\n ) +\n ## manually define quantile transparency\n scale_alpha_manual(\n values = c(`50` = 0.7, `90` = 0.4, `95` = 0.2),\n labels = function(x) paste0(x, \"%\")\n ) +\n labs(\n x = NULL,\n y = NULL,\n alpha = \"Credible\\ninterval\"\n ) +\n scale_x_date(\n date_breaks = \"1 month\",\n date_labels = \"%b %d\\n%Y\"\n ) +\n theme_minimal(base_size = 14) +\n theme(\n strip.background = element_blank(),\n strip.placement = 'outside'\n )", "crumbs": [ "Analysis", "24  Epidemic modeling" @@ -2155,7 +2155,7 @@ "href": "new_pages/contact_tracing.html#preparation", "title": "25  Contact tracing", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # importing data \n here, # relative file pathways \n janitor, # data cleaning and tables\n lubridate, # working with dates\n epikit, # age_categories() function\n apyramid, # age pyramids\n tidyverse, # data manipulation and visualization\n RColorBrewer, # color palettes\n formattable, # fancy tables\n kableExtra # table formatting\n)\n\n\n\nImport data\nWe will import sample datasets of contacts, and of their “follow-up”. These data have been retrieved and un-nested from the Go.Data API and stored as “.rds” files.\nYou can download all the example data for this handbook from the Download handbook and data page.\nIf you want to download the example contact tracing data specific to this page, use the three download links below:\n Click to download the case investigation data (.rds file) \n Click to download the contact registration data (.rds file) \n Click to download the contact follow-up data (.rds file) \n\n\n\nIn their original form in the downloadable files, the data reflect data as provided by the Go.Data API (learn about APIs here). For example purposes here, we will clean the data to make it easier to read on this page. If you are using a Go.Data instance, you can view complete instructions on how to retrieve your data here.\nBelow, the datasets are imported using the import() function from the rio package. See the page on Import and export for various ways to import data. We use here() to specify the file path - you should provide the file path specific to your computer. We then use select() to select only certain columns of the data, to simplify for purposes of demonstration.\n\nCase data\nThese data are a table of the cases, and information about them.\n\ncases <- import(here(\"data\", \"godata\", \"cases_clean.rds\")) %>% \n select(case_id, firstName, lastName, gender, age, age_class,\n occupation, classification, was_contact, hospitalization_typeid)\n\nHere are the nrow(cases) cases:\n\n\n\n\n\n\n\n\nContacts data\nThese data are a table of all the contacts and information about them. Again, provide your own file path. After importing we perform a few preliminary data cleaning steps including:\n\nSet age_class as a factor and reverse the level order so that younger ages are first\n\nSelect only certain column, while re-naming a one of them\n\nArtificially assign rows with missing admin level 2 to “Djembe”, to improve clarity of some example visualisations\n\n\ncontacts <- import(here(\"data\", \"godata\", \"contacts_clean.rds\")) %>% \n mutate(age_class = forcats::fct_rev(age_class)) %>% \n select(contact_id, contact_status, firstName, lastName, gender, age,\n age_class, occupation, date_of_reporting, date_of_data_entry,\n date_of_last_exposure = date_of_last_contact,\n date_of_followup_start, date_of_followup_end, risk_level, was_case, admin_2_name) %>% \n mutate(admin_2_name = replace_na(admin_2_name, \"Djembe\"))\n\nHere are the nrow(contacts) rows of the contacts dataset:\n\n\n\n\n\n\n\n\nFollow-up data\nThese data are records of the “follow-up” interactions with the contacts. Each contact is supposed to have an encounter each day for 14 days after their exposure.\nWe import and perform a few cleaning steps. We select certain columns, and also convert a character column to all lowercase values.\n\nfollowups <- rio::import(here::here(\"data\", \"godata\", \"followups_clean.rds\")) %>% \n select(contact_id, followup_status, followup_number,\n date_of_followup, admin_2_name, admin_1_name) %>% \n mutate(followup_status = str_to_lower(followup_status))\n\nHere are the first 50 rows of the nrow(followups)-row followups dataset (each row is a follow-up interaction, with outcome status in the followup_status column):\n\n\n\n\n\n\n\n\nRelationships data\nHere we import data showing the relationship between cases and contacts. We select certain column to show.\n\nrelationships <- rio::import(here::here(\"data\", \"godata\", \"relationships_clean.rds\")) %>% \n select(source_visualid, source_gender, source_age, date_of_last_contact,\n date_of_data_entry, target_visualid, target_gender,\n target_age, exposure_type)\n\nBelow are the first 50 rows of the relationships dataset, which records all relationships between cases and contacts.", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # importing data \n here, # relative file pathways \n janitor, # data cleaning and tables\n lubridate, # working with dates\n epikit, # age_categories() function\n apyramid, # age pyramids\n tidyverse, # data manipulation and visualization\n RColorBrewer, # color palettes\n formattable, # fancy tables\n kableExtra # table formatting\n)\n\n\n\nImport data\nWe will import sample datasets of contacts, and of their “follow-up”. These data have been retrieved and un-nested from the Go.Data API and stored as “.rds” files.\nYou can download all the example data for this handbook from the Download handbook and data page.\nIf you want to download the example contact tracing data specific to this page, use the three download links below:\n Click to download the case investigation data (.rds file) \n Click to download the contact registration data (.rds file) \n Click to download the contact follow-up data (.rds file) \n\n\n\nIn their original form in the downloadable files, the data reflect data as provided by the Go.Data API (learn about APIs here). For example purposes here, we will clean the data to make it easier to read on this page. If you are using a Go.Data instance, you can view complete instructions on how to retrieve your data here.\nBelow, the datasets are imported using the import() function from the rio package. See the page on Import and export for various ways to import data. We use here() to specify the file path - you should provide the file path specific to your computer. We then use select() to select only certain columns of the data, to simplify for purposes of demonstration.\n\nCase data\nThese data are a table of the cases, and information about them.\n\ncases <- import(here(\"data\", \"godata\", \"cases_clean.rds\")) %>% \n select(case_id, firstName, lastName, gender, age, age_class,\n occupation, classification, was_contact, hospitalization_typeid)\n\nHere are the nrow(cases) cases:\n\n\n\n\n\n\n\n\nContacts data\nThese data are a table of all the contacts and information about them. Again, provide your own file path. After importing we perform a few preliminary data cleaning steps including:\n\nSet age_class as a factor and reverse the level order so that younger ages are first.\n\nSelect only certain column, while re-naming a one of them.\n\nArtificially assign rows with missing admin level 2 to “Djembe”, to improve clarity of some example visualisations.\n\n\ncontacts <- import(here(\"data\", \"godata\", \"contacts_clean.rds\")) %>% \n mutate(age_class = forcats::fct_rev(age_class)) %>% \n select(contact_id, contact_status, firstName, lastName, gender, age,\n age_class, occupation, date_of_reporting, date_of_data_entry,\n date_of_last_exposure = date_of_last_contact,\n date_of_followup_start, date_of_followup_end, risk_level, was_case, admin_2_name) %>% \n mutate(admin_2_name = replace_na(admin_2_name, \"Djembe\"))\n\nHere are the nrow(contacts) rows of the contacts dataset:\n\n\n\n\n\n\n\n\nFollow-up data\nThese data are records of the “follow-up” interactions with the contacts. Each contact is supposed to have an encounter each day for 14 days after their exposure.\nWe import and perform a few cleaning steps. We select certain columns, and also convert a character column to all lowercase values.\n\nfollowups <- rio::import(here::here(\"data\", \"godata\", \"followups_clean.rds\")) %>% \n select(contact_id, followup_status, followup_number,\n date_of_followup, admin_2_name, admin_1_name) %>% \n mutate(followup_status = str_to_lower(followup_status))\n\nHere are the first 50 rows of the nrow(followups)-row followups dataset (each row is a follow-up interaction, with outcome status in the followup_status column):\n\n\n\n\n\n\n\n\nRelationships data\nHere we import data showing the relationship between cases and contacts. We select certain column to show.\n\nrelationships <- rio::import(here::here(\"data\", \"godata\", \"relationships_clean.rds\")) %>% \n select(source_visualid, source_gender, source_age, date_of_last_contact,\n date_of_data_entry, target_visualid, target_gender,\n target_age, exposure_type)\n\nWarning: Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\n\n\nBelow are the first 50 rows of the relationships dataset, which records all relationships between cases and contacts.", "crumbs": [ "Analysis", "25  Contact tracing" @@ -2166,7 +2166,7 @@ "href": "new_pages/contact_tracing.html#descriptive-analyses", "title": "25  Contact tracing", "section": "25.2 Descriptive analyses", - "text": "25.2 Descriptive analyses\nYou can use the techniques covered in other pages of this handbook to conduct descriptive analyses of your cases, contacts, and their relationships. Below are some examples.\n\nDemographics\nAs demonstrated in the page covering Demographic pyramids, you can visualise the age and gender distribution (here we use the apyramid package).\n\nAge and Gender of contacts\nThe pyramid below compares the age distribution of contacts, by gender. Note that contacts missing age are included in their own bar at the top. You can change this default behavior, but then consider listing the number missing in a caption.\n\napyramid::age_pyramid(\n data = contacts, # use contacts dataset\n age_group = \"age_class\", # categorical age column\n split_by = \"gender\") + # gender for halfs of pyramid\n labs(\n fill = \"Gender\", # title of legend\n title = \"Age/Sex Pyramid of COVID-19 contacts\")+ # title of the plot\n theme_minimal() # simple background\n\n\n\n\n\n\n\n\nWith the Go.Data data structure, the relationships dataset contains the ages of both cases and contacts, so you could use that dataset and create an age pyramid showing the differences between these two groups of people. The relationships data frame will be mutated to transform the numberic age columns into categories (see the Cleaning data and core functions page). We also pivot the dataframe longer to facilitate easy plotting with ggplot2 (see Pivoting data).\n\nrelation_age <- relationships %>% \n select(source_age, target_age) %>% \n transmute( # transmute is like mutate() but removes all other columns not mentioned\n source_age_class = epikit::age_categories(source_age, breakers = seq(0, 80, 5)),\n target_age_class = epikit::age_categories(target_age, breakers = seq(0, 80, 5)),\n ) %>% \n pivot_longer(cols = contains(\"class\"), names_to = \"category\", values_to = \"age_class\") # pivot longer\n\n\nrelation_age\n\n# A tibble: 200 × 2\n category age_class\n <chr> <fct> \n 1 source_age_class 80+ \n 2 target_age_class 15-19 \n 3 source_age_class <NA> \n 4 target_age_class 50-54 \n 5 source_age_class <NA> \n 6 target_age_class 20-24 \n 7 source_age_class 30-34 \n 8 target_age_class 45-49 \n 9 source_age_class 40-44 \n10 target_age_class 30-34 \n# ℹ 190 more rows\n\n\nNow we can plot this transformed dataset with age_pyramid() as before, but replacing gender with category (contact, or case).\n\napyramid::age_pyramid(\n data = relation_age, # use modified relationship dataset\n age_group = \"age_class\", # categorical age column\n split_by = \"category\") + # by cases and contacts\n scale_fill_manual(\n values = c(\"orange\", \"purple\"), # to specify colors AND labels\n labels = c(\"Case\", \"Contact\"))+\n labs(\n fill = \"Legend\", # title of legend\n title = \"Age/Sex Pyramid of COVID-19 contacts and cases\")+ # title of the plot\n theme_minimal() # simple background\n\n\n\n\n\n\n\n\nWe can also view other characteristics such as occupational breakdown (e.g. in form of a pie chart).\n\n# Clean dataset and get counts by occupation\nocc_plot_data <- cases %>% \n mutate(occupation = forcats::fct_explicit_na(occupation), # make NA missing values a category\n occupation = forcats::fct_infreq(occupation)) %>% # order factor levels in order of frequency\n count(occupation) # get counts by occupation\n \n# Make pie chart\nggplot(data = occ_plot_data, mapping = aes(x = \"\", y = n, fill = occupation))+\n geom_bar(width = 1, stat = \"identity\") +\n coord_polar(\"y\", start = 0) +\n labs(\n fill = \"Occupation\",\n title = \"Known occupations of COVID-19 cases\")+\n theme_minimal() + \n theme(axis.line = element_blank(),\n axis.title = element_blank(),\n axis.text = element_blank())\n\n\n\n\n\n\n\n\n\n\n\nContacts per case\nThe number of contacts per case can be an important metric to assess quality of contact enumeration and the compliance of the population toward public health response.\nDepending on your data structure, this can be assessed with a dataset that contains all cases and contacts. In the Go.Data datasets, the links between cases (“sources”) and contacts (“targets”) is stored in the relationships dataset.\nIn this dataset, each row is a contact, and the source case is listed in the row. There are no contacts who have relationships with multiple cases, but if this exists you may need to account for those before plotting (and explore them too!).\nWe begin by counting the number of rows (contacts) per source case. This is saved as a data frame.\n\ncontacts_per_case <- relationships %>% \n count(source_visualid)\n\ncontacts_per_case\n\n source_visualid n\n1 CASE-2020-0001 13\n2 CASE-2020-0002 5\n3 CASE-2020-0003 2\n4 CASE-2020-0004 4\n5 CASE-2020-0005 5\n6 CASE-2020-0006 3\n7 CASE-2020-0008 3\n8 CASE-2020-0009 3\n9 CASE-2020-0010 3\n10 CASE-2020-0012 3\n11 CASE-2020-0013 5\n12 CASE-2020-0014 3\n13 CASE-2020-0016 3\n14 CASE-2020-0018 4\n15 CASE-2020-0022 3\n16 CASE-2020-0023 4\n17 CASE-2020-0030 3\n18 CASE-2020-0031 3\n19 CASE-2020-0034 4\n20 CASE-2020-0036 1\n21 CASE-2020-0037 3\n22 CASE-2020-0045 3\n23 <NA> 17\n\n\nWe use geom_histogram() to plot these data as a histogram.\n\nggplot(data = contacts_per_case)+ # begin with count data frame created above\n geom_histogram(mapping = aes(x = n))+ # print histogram of number of contacts per case\n scale_y_continuous(expand = c(0,0))+ # remove excess space below 0 on y-axis\n theme_light()+ # simplify background\n labs(\n title = \"Number of contacts per case\",\n y = \"Cases\",\n x = \"Contacts per case\"\n )", + "text": "25.2 Descriptive analyses\nYou can use the techniques covered in other pages of this handbook to conduct descriptive analyses of your cases, contacts, and their relationships. Below are some examples.\n\nDemographics\nAs demonstrated in the page covering Demographic pyramids, you can visualise the age and gender distribution (here we use the apyramid package).\n\nAge and Gender of contacts\nThe pyramid below compares the age distribution of contacts, by gender. Note that contacts missing age are included in their own bar at the top. You can change this default behavior, but then consider listing the number missing in a caption.\n\napyramid::age_pyramid(\n data = contacts, # use contacts dataset\n age_group = \"age_class\", # categorical age column\n split_by = \"gender\") + # gender for halfs of pyramid\n labs(\n fill = \"Gender\", # title of legend\n title = \"Age/Sex Pyramid of COVID-19 contacts\") + # title of the plot\n theme_minimal() # simple background\n\n\n\n\n\n\n\n\nWith the Go.Data data structure, the relationships dataset contains the ages of both cases and contacts, so you could use that dataset and create an age pyramid showing the differences between these two groups of people. The relationships data frame will be mutated to transform the numberic age columns into categories (see the Cleaning data and core functions page). We also pivot the dataframe longer to facilitate easy plotting with ggplot2 (see Pivoting data).\n\nrelation_age <- relationships %>% \n select(source_age, target_age) %>% \n transmute( # transmute is like mutate() but removes all other columns not mentioned\n source_age_class = epikit::age_categories(source_age, breakers = seq(0, 80, 5)),\n target_age_class = epikit::age_categories(target_age, breakers = seq(0, 80, 5)),\n ) %>% \n pivot_longer(cols = contains(\"class\"), names_to = \"category\", values_to = \"age_class\") # pivot longer\n\n\nrelation_age\n\n# A tibble: 200 × 2\n category age_class\n <chr> <fct> \n 1 source_age_class 80+ \n 2 target_age_class 15-19 \n 3 source_age_class <NA> \n 4 target_age_class 50-54 \n 5 source_age_class <NA> \n 6 target_age_class 20-24 \n 7 source_age_class 30-34 \n 8 target_age_class 45-49 \n 9 source_age_class 40-44 \n10 target_age_class 30-34 \n# ℹ 190 more rows\n\n\nNow we can plot this transformed dataset with age_pyramid() as before, but replacing gender with category (contact, or case).\n\napyramid::age_pyramid(\n data = relation_age, # use modified relationship dataset\n age_group = \"age_class\", # categorical age column\n split_by = \"category\") + # by cases and contacts\n scale_fill_manual(\n values = c(\"orange\", \"purple\"), # to specify colors AND labels\n labels = c(\"Case\", \"Contact\")) +\n labs(\n fill = \"Legend\", # title of legend\n title = \"Age/Sex Pyramid of COVID-19 contacts and cases\") + # title of the plot\n theme_minimal() # simple background\n\n\n\n\n\n\n\n\nWe can also view other characteristics such as occupational breakdown (e.g. in form of a pie chart).\n\n# Clean dataset and get counts by occupation\nocc_plot_data <- cases %>% \n mutate(occupation = forcats::fct_explicit_na(occupation), # make NA missing values a category\n occupation = forcats::fct_infreq(occupation)) %>% # order factor levels in order of frequency\n count(occupation) # get counts by occupation\n \n# Make pie chart\nggplot(data = occ_plot_data, mapping = aes(x = \"\", y = n, fill = occupation)) +\n geom_bar(width = 1, stat = \"identity\") +\n coord_polar(\"y\", start = 0) +\n labs(\n fill = \"Occupation\",\n title = \"Known occupations of COVID-19 cases\") +\n theme_minimal() + \n theme(axis.line = element_blank(),\n axis.title = element_blank(),\n axis.text = element_blank())\n\n\n\n\n\n\n\n\n\n\n\nContacts per case\nThe number of contacts per case can be an important metric to assess quality of contact enumeration and the compliance of the population toward public health response.\nDepending on your data structure, this can be assessed with a dataset that contains all cases and contacts. In the Go.Data datasets, the links between cases (“sources”) and contacts (“targets”) is stored in the relationships dataset.\nIn this dataset, each row is a contact, and the source case is listed in the row. There are no contacts who have relationships with multiple cases, but if this exists you may need to account for those before plotting (and explore them too!).\nWe begin by counting the number of rows (contacts) per source case. This is saved as a data frame.\n\ncontacts_per_case <- relationships %>% \n count(source_visualid)\n\ncontacts_per_case\n\n source_visualid n\n1 CASE-2020-0001 13\n2 CASE-2020-0002 5\n3 CASE-2020-0003 2\n4 CASE-2020-0004 4\n5 CASE-2020-0005 5\n6 CASE-2020-0006 3\n7 CASE-2020-0008 3\n8 CASE-2020-0009 3\n9 CASE-2020-0010 3\n10 CASE-2020-0012 3\n11 CASE-2020-0013 5\n12 CASE-2020-0014 3\n13 CASE-2020-0016 3\n14 CASE-2020-0018 4\n15 CASE-2020-0022 3\n16 CASE-2020-0023 4\n17 CASE-2020-0030 3\n18 CASE-2020-0031 3\n19 CASE-2020-0034 4\n20 CASE-2020-0036 1\n21 CASE-2020-0037 3\n22 CASE-2020-0045 3\n23 <NA> 17\n\n\nWe use geom_histogram() to plot these data as a histogram.\n\nggplot(data = contacts_per_case) + # begin with count data frame created above\n geom_histogram(mapping = aes(x = n)) + # print histogram of number of contacts per case\n scale_y_continuous(expand = c(0,0)) + # remove excess space below 0 on y-axis\n theme_light() + # simplify background\n labs(\n title = \"Number of contacts per case\",\n y = \"Cases\",\n x = \"Contacts per case\"\n )", "crumbs": [ "Analysis", "25  Contact tracing" @@ -2177,7 +2177,7 @@ "href": "new_pages/contact_tracing.html#contact-follow-up", "title": "25  Contact tracing", "section": "25.3 Contact Follow Up", - "text": "25.3 Contact Follow Up\nContact tracing data often contain “follow-up” data, which record outcomes of daily symptom checks of persons in quarantine. Analysis of this data can inform response strategy, identify contacts at-risk of loss-to-follow-up or at-risk of developing disease.\n\nData cleaning\nThese data can exist in a variety of formats. They may exist as a “wide” format Excel sheet with one row per contact, and one column per follow-up “day”. See Pivoting data for descriptions of “long” and “wide” data and how to pivot data wider or longer.\nIn our Go.Data example, these data are stored in the followups data frame, which is in a “long” format with one row per follow-up interaction. The first 50 rows look like this:\n\n\n\n\n\n\nCAUTION: Beware of duplicates when dealing with followup data; as there could be several erroneous followups on the same day for a given contact. Perhaps it seems to be an error but reflects reality - e.g. a contact tracer could submit a follow-up form early in the day when they could not reach the contact, and submit a second form when they were later reached. It will depend on the operational context for how you want to handle duplicates - just make sure to document your approach clearly. \nLet’s see how many instances of “duplicate” rows we have:\n\nfollowups %>% \n count(contact_id, date_of_followup) %>% # get unique contact_days\n filter(n > 1) # view records where count is more than 1 \n\n contact_id date_of_followup n\n1 <NA> 2020-09-03 2\n2 <NA> 2020-09-04 2\n3 <NA> 2020-09-05 2\n\n\nIn our example data, the only records that this applies to are ones missing an ID! We can remove those. But, for purposes of demonstration we will go show the steps for de-duplication so there is only one follow-up encoutner per person per day. See the page on De-duplication for more detail. We will assume that the most recent encounter record is the correct one. We also take the opportunity to clean the followup_number column (the “day” of follow-up which should range 1 - 14).\n\nfollowups_clean <- followups %>%\n \n # De-duplicate\n group_by(contact_id, date_of_followup) %>% # group rows per contact-day\n arrange(contact_id, desc(date_of_followup)) %>% # arrange rows, per contact-day, by date of follow-up (most recent at top)\n slice_head() %>% # keep only the first row per unique contact id \n ungroup() %>% \n \n # Other cleaning\n mutate(followup_number = replace(followup_number, followup_number > 14, NA)) %>% # clean erroneous data\n drop_na(contact_id) # remove rows with missing contact_id\n\nFor each follow-up encounter, we have a follow-up status (such as whether the encounter occurred and if so, did the contact have symptoms or not). To see all the values we can run a quick tabyl() (from janitor) or table() (from base R) (see Descriptive tables) by followup_status to see the frequency of each of the outcomes.\nIn this dataset, “seen_not_ok” means “seen with symptoms”, and “seen_ok” means “seen without symptoms”.\n\nfollowups_clean %>% \n tabyl(followup_status)\n\n followup_status n percent\n missed 10 0.02325581\n not_attempted 5 0.01162791\n not_performed 319 0.74186047\n seen_not_ok 6 0.01395349\n seen_ok 90 0.20930233\n\n\n\n\nPlot over time\nAs the dates data are continuous, we will use a histogram to plot them with date_of_followup assigned to the x-axis. We can achieve a “stacked” histogram by specifying a fill = argument within aes(), which we assign to the column followup_status. Consequently, you can set the legend title using the fill = argument of labs().\nWe can see that the contacts were identified in waves (presumably corresponding with epidemic waves of cases), and that follow-up completion did not seemingly improve over the course of the epidemic.\n\nggplot(data = followups_clean)+\n geom_histogram(mapping = aes(x = date_of_followup, fill = followup_status)) +\n scale_fill_discrete(drop = FALSE)+ # show all factor levels (followup_status) in the legend, even those not used\n theme_classic() +\n labs(\n x = \"\",\n y = \"Number of contacts\",\n title = \"Daily Contact Followup Status\",\n fill = \"Followup Status\",\n subtitle = str_glue(\"Data as of {max(followups$date_of_followup, na.rm=T)}\")) # dynamic subtitle\n\n\n\n\n\n\n\n\nCAUTION: If you are preparing many plots (e.g. for multiple jurisdictions) you will want the legends to appear identically even with varying levels of data completion or data composition. There may be plots for which not all follow-up statuses are present in the data, but you still want those categories to appear the legends. In ggplots (like above), you can specify the drop = FALSE argument of the scale_fill_discrete(). In tables, use tabyl() which shows counts for all factor levels, or if using count() from dplyr add the argument .drop = FALSE to include counts for all factor levels.\n\n\nDaily individual tracking\nIf your outbreak is small enough, you may want to look at each contact individually and see their status over the course of their follow-up. Fortunately, this followups dataset already contains a column with the day “number” of follow-up (1-14). If this does not exist in your data, you could create it by calculating the difference between the encounter date and the date follow-up was intended to begin for the contact.\nA convenient visualisation mechanism (if the number of cases is not too large) can be a heat plot, made with geom_tile(). See more details in the heat plot page.\n\nggplot(data = followups_clean)+\n geom_tile(mapping = aes(x = followup_number, y = contact_id, fill = followup_status),\n color = \"grey\")+ # grey gridlines\n scale_fill_manual( values = c(\"yellow\", \"grey\", \"orange\", \"darkred\", \"darkgreen\"))+\n theme_minimal()+\n scale_x_continuous(breaks = seq(from = 1, to = 14, by = 1))\n\n\n\n\n\n\n\n\n\n\nAnalyse by group\nPerhaps these follow-up data are being viewed on a daily or weekly basis for operational decision-making. You may want more meaningful disaggregations by geographic area or by contact-tracing team. We can do this by adjusting the columns provided to group_by().\n\nplot_by_region <- followups_clean %>% # begin with follow-up dataset\n count(admin_1_name, admin_2_name, followup_status) %>% # get counts by unique region-status (creates column 'n' with counts)\n \n # begin ggplot()\n ggplot( # begin ggplot\n mapping = aes(x = reorder(admin_2_name, n), # reorder admin factor levels by the numeric values in column 'n'\n y = n, # heights of bar from column 'n'\n fill = followup_status, # color stacked bars by their status\n label = n))+ # to pass to geom_label() \n geom_col()+ # stacked bars, mapping inherited from above \n geom_text( # add text, mapping inherited from above\n size = 3, \n position = position_stack(vjust = 0.5), \n color = \"white\", \n check_overlap = TRUE,\n fontface = \"bold\")+\n coord_flip()+\n labs(\n x = \"\",\n y = \"Number of contacts\",\n title = \"Contact Followup Status, by Region\",\n fill = \"Followup Status\",\n subtitle = str_glue(\"Data as of {max(followups_clean$date_of_followup, na.rm=T)}\")) +\n theme_classic()+ # Simplify background\n facet_wrap(~admin_1_name, strip.position = \"right\", scales = \"free_y\", ncol = 1) # introduce facets \n\nplot_by_region", + "text": "25.3 Contact Follow Up\nContact tracing data often contain “follow-up” data, which record outcomes of daily symptom checks of persons in quarantine. Analysis of this data can inform response strategy, identify contacts at-risk of loss-to-follow-up or at-risk of developing disease.\n\nData cleaning\nThese data can exist in a variety of formats. They may exist as a “wide” format Excel sheet with one row per contact, and one column per follow-up “day”. See Pivoting data for descriptions of “long” and “wide” data and how to pivot data wider or longer.\nIn our Go.Data example, these data are stored in the followups data frame, which is in a “long” format with one row per follow-up interaction. The first 50 rows look like this:\n\n\n\n\n\n\nCAUTION: Beware of duplicates when dealing with followup data; as there could be several erroneous followups on the same day for a given contact. Perhaps it seems to be an error but reflects reality - e.g. a contact tracer could submit a follow-up form early in the day when they could not reach the contact, and submit a second form when they were later reached. It will depend on the operational context for how you want to handle duplicates - just make sure to document your approach clearly. \nLet’s see how many instances of “duplicate” rows we have:\n\nfollowups %>% \n count(contact_id, date_of_followup) %>% # get unique contact_days\n filter(n > 1) # view records where count is more than 1 \n\n contact_id date_of_followup n\n1 <NA> 2020-09-03 2\n2 <NA> 2020-09-04 2\n3 <NA> 2020-09-05 2\n\n\nIn our example data, the only records that this applies to are ones missing an ID! We can remove those. But, for purposes of demonstration we will go show the steps for de-duplication so there is only one follow-up encounter per person per day. See the page on De-duplication for more detail. We will assume that the most recent encounter record is the correct one. We also take the opportunity to clean the followup_number column (the “day” of follow-up which should range 1 - 14).\n\nfollowups_clean <- followups %>%\n \n # De-duplicate\n group_by(contact_id, date_of_followup) %>% # group rows per contact-day\n arrange(contact_id, desc(date_of_followup)) %>% # arrange rows, per contact-day, by date of follow-up (most recent at top)\n slice_head() %>% # keep only the first row per unique contact id \n ungroup() %>% \n \n # Other cleaning\n mutate(followup_number = replace(followup_number, followup_number > 14, NA)) %>% # clean erroneous data\n drop_na(contact_id) # remove rows with missing contact_id\n\nFor each follow-up encounter, we have a follow-up status (such as whether the encounter occurred and if so, did the contact have symptoms or not). To see all the values we can run a quick tabyl() (from janitor) or table() (from base R) (see Descriptive tables) by followup_status to see the frequency of each of the outcomes.\nIn this dataset, “seen_not_ok” means “seen with symptoms”, and “seen_ok” means “seen without symptoms”.\n\nfollowups_clean %>% \n tabyl(followup_status)\n\n followup_status n percent\n missed 10 0.02325581\n not_attempted 5 0.01162791\n not_performed 319 0.74186047\n seen_not_ok 6 0.01395349\n seen_ok 90 0.20930233\n\n\n\n\nPlot over time\nAs the dates data are continuous, we will use a histogram to plot them with date_of_followup assigned to the x-axis. We can achieve a “stacked” histogram by specifying a fill = argument within aes(), which we assign to the column followup_status. Consequently, you can set the legend title using the fill = argument of labs().\nWe can see that the contacts were identified in waves (presumably corresponding with epidemic waves of cases), and that follow-up completion did not seemingly improve over the course of the epidemic.\n\nggplot(data = followups_clean) +\n geom_histogram(mapping = aes(x = date_of_followup, fill = followup_status)) +\n scale_fill_discrete(drop = FALSE) + # show all factor levels (followup_status) in the legend, even those not used\n theme_classic() +\n labs(\n x = \"\",\n y = \"Number of contacts\",\n title = \"Daily Contact Followup Status\",\n fill = \"Followup Status\",\n subtitle = str_glue(\"Data as of {max(followups$date_of_followup, na.rm=T)}\")) # dynamic subtitle\n\n\n\n\n\n\n\n\nCAUTION: If you are preparing many plots (e.g. for multiple jurisdictions) you will want the legends to appear identically even with varying levels of data completion or data composition. There may be plots for which not all follow-up statuses are present in the data, but you still want those categories to appear the legends. In ggplots (like above), you can specify the drop = FALSE argument of the scale_fill_discrete(). In tables, use tabyl() which shows counts for all factor levels, or if using count() from dplyr add the argument .drop = FALSE to include counts for all factor levels.\n\n\nDaily individual tracking\nIf your outbreak is small enough, you may want to look at each contact individually and see their status over the course of their follow-up. Fortunately, this followups dataset already contains a column with the day “number” of follow-up (1-14). If this does not exist in your data, you could create it by calculating the difference between the encounter date and the date follow-up was intended to begin for the contact.\nA convenient visualisation mechanism (if the number of cases is not too large) can be a heat plot, made with geom_tile(). See more details in the heat plot page.\n\nggplot(data = followups_clean) +\n geom_tile(mapping = aes(x = followup_number, y = contact_id, fill = followup_status),\n color = \"grey\") + # grey gridlines\n scale_fill_manual( values = c(\"yellow\", \"grey\", \"orange\", \"darkred\", \"darkgreen\")) +\n theme_minimal() +\n scale_x_continuous(breaks = seq(from = 1, to = 14, by = 1))\n\n\n\n\n\n\n\n\n\n\nAnalyse by group\nPerhaps these follow-up data are being viewed on a daily or weekly basis for operational decision-making. You may want more meaningful disaggregations by geographic area or by contact-tracing team. We can do this by adjusting the columns provided to group_by().\n\nplot_by_region <- followups_clean %>% # begin with follow-up dataset\n count(admin_1_name, admin_2_name, followup_status) %>% # get counts by unique region-status (creates column 'n' with counts)\n \n # begin ggplot()\n ggplot( # begin ggplot\n mapping = aes(x = reorder(admin_2_name, n), # reorder admin factor levels by the numeric values in column 'n'\n y = n, # heights of bar from column 'n'\n fill = followup_status, # color stacked bars by their status\n label = n)) + # to pass to geom_label() \n geom_col() + # stacked bars, mapping inherited from above \n geom_text( # add text, mapping inherited from above\n size = 3, \n position = position_stack(vjust = 0.5), \n color = \"white\", \n check_overlap = TRUE,\n fontface = \"bold\") +\n coord_flip() +\n labs(\n x = \"\",\n y = \"Number of contacts\",\n title = \"Contact Followup Status, by Region\",\n fill = \"Followup Status\",\n subtitle = str_glue(\"Data as of {max(followups_clean$date_of_followup, na.rm=T)}\")) +\n theme_classic() + # Simplify background\n facet_wrap(~admin_1_name, strip.position = \"right\", scales = \"free_y\", ncol = 1) # introduce facets \n\nplot_by_region", "crumbs": [ "Analysis", "25  Contact tracing" @@ -2188,7 +2188,7 @@ "href": "new_pages/contact_tracing.html#kpi-tables", "title": "25  Contact tracing", "section": "25.4 KPI Tables", - "text": "25.4 KPI Tables\nThere are a number of different Key Performance Indicators (KPIs) that can be calculated and tracked at varying levels of disaggregations and across different time periods to monitor contact tracing performance. Once you have the calculations down and the basic table format; it is fairly easy to swap in and out different KPIs.\nThere are numerous sources of contact tracing KPIs, such as this one from ResolveToSaveLives.org. The majority of the work will be walking through your data structure and thinking through all of the inclusion/exclusion criteria. We show a few examples below; using Go.Data metadata structure:\n\n\n\n\n\n\n\n\n\nCategory\nIndicator\nGo.Data Numerator\nGo.Data Denominator\n\n\n\n\nProcess Indicator - Speed of Contact Tracing\n% cases interviewed and isolated within 24h of case report\nCOUNT OF case_id WHERE (date_of_reporting - date_of_data_entry) < 1 day AND (isolation_startdate - date_of_data_entry) < 1 day\nCOUNT OF case_id\n\n\nProcess Indicator - Speed of Contact Tracing\n% contacts notified and quarantined within 24h of elicitation\nCOUNT OF contact_id WHERE followup_status == “SEEN_NOT_OK” OR “SEEN_OK” AND date_of_followup - date_of_reporting < 1 day\nCOUNT OF contact_id\n\n\nProcess Indicator - Completeness of Testing\n% new symptomatic cases tested and interviewed within 3 days of onset of symptoms\nCOUNT OF case_id WHERE (date_of_reporting - date_of_onset) < =3 days\nCOUNT OF case_id\n\n\nOutcome Indicator - Overall\n% new cases among existing contact list\nCOUNT OF case_id WHERE was_contact == “TRUE”\nCOUNT OF case_id\n\n\n\nBelow we will walk through a sample exercise of creating a nice table visual to show contact follow-up across admin areas. At the end, we will make it fit for presentation with the formattable package (but you could use other packages like flextable - see Tables for presentation).\nHow you create a table like this will depend on the structure of your contact tracing data. Use the Descriptive tables page to learn how to summarise data using dplyr functions.\nWe will create a table that will be dynamic and change as the data change. To make the results interesting, we will set a report_date to allow us to simulate running the table on a certain day (we pick 10th June 2020). The data are filtered to that date.\n\n# Set \"Report date\" to simulate running the report with data \"as of\" this date\nreport_date <- as.Date(\"2020-06-10\")\n\n# Create follow-up data to reflect the report date.\ntable_data <- followups_clean %>% \n filter(date_of_followup <= report_date)\n\nNow, based on our data structure, we will do the following:\n\nBegin with the followups data and summarise it to contain, for each unique contact:\n\n\n\nThe date of latest record (no matter the status of the encounter)\n\nThe date of latest encounter where the contact was “seen”\n\nThe encounter status at that final “seen” encounter (e.g. with symptoms, without symptoms)\n\n\n\nJoin these data to the contacts data, which contains other information such as the overall contact status, date of last exposure to a case, etc. Also we will calculate metrics of interest for each contact such as days since last exposure\n\nWe group the enhanced contact data by geographic region (admin_2_name) and calculate summary statistics per region\n\nFinally, we format the table nicely for presentation\n\nFirst we summarise the follow-up data to get the information of interest:\n\nfollowup_info <- table_data %>% \n group_by(contact_id) %>% \n summarise(\n date_last_record = max(date_of_followup, na.rm=T),\n date_last_seen = max(date_of_followup[followup_status %in% c(\"seen_ok\", \"seen_not_ok\")], na.rm=T),\n status_last_record = followup_status[which(date_of_followup == date_last_record)]) %>% \n ungroup()\n\nHere is how these data look:\n\n\n\n\n\n\nNow we will add this information to the contacts dataset, and calculate some additional columns.\n\ncontacts_info <- followup_info %>% \n right_join(contacts, by = \"contact_id\") %>% \n mutate(\n database_date = max(date_last_record, na.rm=T),\n days_since_seen = database_date - date_last_seen,\n days_since_exposure = database_date - date_of_last_exposure\n )\n\nHere is how these data look. Note contacts column to the right, and new calculated column at the far right.\n\n\n\n\n\n\nNext we summarise the contacts data by region, to achieve a concise data frame of summary statistic columns.\n\ncontacts_table <- contacts_info %>% \n \n group_by(`Admin 2` = admin_2_name) %>%\n \n summarise(\n `Registered contacts` = n(),\n `Active contacts` = sum(contact_status == \"UNDER_FOLLOW_UP\", na.rm=T),\n `In first week` = sum(days_since_exposure < 8, na.rm=T),\n `In second week` = sum(days_since_exposure >= 8 & days_since_exposure < 15, na.rm=T),\n `Became case` = sum(contact_status == \"BECAME_CASE\", na.rm=T),\n `Lost to follow up` = sum(days_since_seen >= 3, na.rm=T),\n `Never seen` = sum(is.na(date_last_seen)),\n `Followed up - signs` = sum(status_last_record == \"Seen_not_ok\" & date_last_record == database_date, na.rm=T),\n `Followed up - no signs` = sum(status_last_record == \"Seen_ok\" & date_last_record == database_date, na.rm=T),\n `Not Followed up` = sum(\n (status_last_record == \"NOT_ATTEMPTED\" | status_last_record == \"NOT_PERFORMED\") &\n date_last_record == database_date, na.rm=T)) %>% \n \n arrange(desc(`Registered contacts`))\n\n\n\n\n\n\n\nAnd now we apply styling from the formattable and knitr packages, including a footnote that shows the “as of” date.\n\ncontacts_table %>%\n mutate(\n `Admin 2` = formatter(\"span\", style = ~ formattable::style(\n color = ifelse(`Admin 2` == NA, \"red\", \"grey\"),\n font.weight = \"bold\",font.style = \"italic\"))(`Admin 2`),\n `Followed up - signs`= color_tile(\"white\", \"orange\")(`Followed up - signs`),\n `Followed up - no signs`= color_tile(\"white\", \"#A0E2BD\")(`Followed up - no signs`),\n `Became case`= color_tile(\"white\", \"grey\")(`Became case`),\n `Lost to follow up`= color_tile(\"white\", \"grey\")(`Lost to follow up`), \n `Never seen`= color_tile(\"white\", \"red\")(`Never seen`),\n `Active contacts` = color_tile(\"white\", \"#81A4CE\")(`Active contacts`)\n ) %>%\n kable(\"html\", escape = F, align =c(\"l\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\")) %>%\n kable_styling(\"hover\", full_width = FALSE) %>%\n add_header_above(c(\" \" = 3, \n \"Of contacts currently under follow up\" = 5,\n \"Status of last visit\" = 3)) %>% \n kableExtra::footnote(general = str_glue(\"Data are current to {format(report_date, '%b %d %Y')}\"))\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOf contacts currently under follow up\n\n\nStatus of last visit\n\n\n\nAdmin 2\nRegistered contacts\nActive contacts\nIn first week\nIn second week\nBecame case\nLost to follow up\nNever seen\nFollowed up - signs\nFollowed up - no signs\nNot Followed up\n\n\n\n\nDjembe \n59\n30\n44\n0\n2\n15\n22\n0\n0\n0\n\n\nTrumpet\n3\n1\n3\n0\n0\n0\n0\n0\n0\n0\n\n\nVenu \n2\n0\n0\n0\n2\n0\n2\n0\n0\n0\n\n\nCongas \n1\n0\n0\n0\n1\n0\n1\n0\n0\n0\n\n\nCornet \n1\n0\n1\n0\n1\n0\n1\n0\n0\n0\n\n\n\nNote: \n\n\n\n\n\n\n\n\n\n\n\n\n Data are current to Jun 10 2020", + "text": "25.4 KPI Tables\nThere are a number of different Key Performance Indicators (KPIs) that can be calculated and tracked at varying levels of disaggregations and across different time periods to monitor contact tracing performance. Once you have the calculations down and the basic table format; it is fairly easy to swap in and out different KPIs.\nThere are numerous sources of contact tracing KPIs, such as this one from ResolveToSaveLives.org. The majority of the work will be walking through your data structure and thinking through all of the inclusion/exclusion criteria. We show a few examples below; using Go.Data metadata structure:\n\n\n\n\n\n\n\n\n\nCategory\nIndicator\nGo.Data Numerator\nGo.Data Denominator\n\n\n\n\nProcess Indicator - Speed of Contact Tracing\n% cases interviewed and isolated within 24h of case report\nCOUNT OF case_id WHERE (date_of_reporting - date_of_data_entry) < 1 day AND (isolation_startdate - date_of_data_entry) < 1 day\nCOUNT OF case_id\n\n\nProcess Indicator - Speed of Contact Tracing\n% contacts notified and quarantined within 24h of elicitation\nCOUNT OF contact_id WHERE followup_status == “SEEN_NOT_OK” OR “SEEN_OK” AND date_of_followup - date_of_reporting < 1 day\nCOUNT OF contact_id\n\n\nProcess Indicator - Completeness of Testing\n% new symptomatic cases tested and interviewed within 3 days of onset of symptoms\nCOUNT OF case_id WHERE (date_of_reporting - date_of_onset) < =3 days\nCOUNT OF case_id\n\n\nOutcome Indicator - Overall\n% new cases among existing contact list\nCOUNT OF case_id WHERE was_contact == “TRUE”\nCOUNT OF case_id\n\n\n\nBelow we will walk through a sample exercise of creating a nice table visual to show contact follow-up across admin areas. At the end, we will make it fit for presentation with the formattable package (but you could use other packages like flextable - see Tables for presentation).\nHow you create a table like this will depend on the structure of your contact tracing data. Use the Descriptive tables page to learn how to summarise data using dplyr functions.\nWe will create a table that will be dynamic and change as the data change. To make the results interesting, we will set a report_date to allow us to simulate running the table on a certain day (we pick 10th June 2020). The data are filtered to that date.\n\n# Set \"Report date\" to simulate running the report with data \"as of\" this date\nreport_date <- as.Date(\"2020-06-10\")\n\n# Create follow-up data to reflect the report date.\ntable_data <- followups_clean %>% \n filter(date_of_followup <= report_date)\n\nNow, based on our data structure, we will do the following:\n\nBegin with the followups data and summarise it to contain, for each unique contact:\n\n\n\nThe date of latest record (no matter the status of the encounter).\n\nThe date of latest encounter where the contact was “seen”.\n\nThe encounter status at that final “seen” encounter (e.g. with symptoms, without symptoms).\n\n\n\nJoin these data to the contacts data, which contains other information such as the overall contact status, date of last exposure to a case, etc. Also we will calculate metrics of interest for each contact such as days since last exposure.\n\nWe group the enhanced contact data by geographic region (admin_2_name) and calculate summary statistics per region.\n\nFinally, we format the table nicely for presentation.\n\nFirst we summarise the follow-up data to get the information of interest:\n\nfollowup_info <- table_data %>% \n group_by(contact_id) %>% \n summarise(\n date_last_record = max(date_of_followup, na.rm=T),\n date_last_seen = max(date_of_followup[followup_status %in% c(\"seen_ok\", \"seen_not_ok\")], na.rm=T),\n status_last_record = followup_status[which(date_of_followup == date_last_record)]) %>% \n ungroup()\n\nHere is how these data look:\n\n\n\n\n\n\nNow we will add this information to the contacts dataset, and calculate some additional columns.\n\ncontacts_info <- followup_info %>% \n right_join(contacts, by = \"contact_id\") %>% \n mutate(\n database_date = max(date_last_record, na.rm=T),\n days_since_seen = database_date - date_last_seen,\n days_since_exposure = database_date - date_of_last_exposure\n )\n\nHere is how these data look. Note contacts column to the right, and new calculated column at the far right.\n\n\n\n\n\n\nNext we summarise the contacts data by region, to achieve a concise data frame of summary statistic columns.\n\ncontacts_table <- contacts_info %>% \n \n group_by(`Admin 2` = admin_2_name) %>%\n \n summarise(\n `Registered contacts` = n(),\n `Active contacts` = sum(contact_status == \"UNDER_FOLLOW_UP\", na.rm=T),\n `In first week` = sum(days_since_exposure < 8, na.rm=T),\n `In second week` = sum(days_since_exposure >= 8 & days_since_exposure < 15, na.rm=T),\n `Became case` = sum(contact_status == \"BECAME_CASE\", na.rm=T),\n `Lost to follow up` = sum(days_since_seen >= 3, na.rm=T),\n `Never seen` = sum(is.na(date_last_seen)),\n `Followed up - signs` = sum(status_last_record == \"Seen_not_ok\" & date_last_record == database_date, na.rm=T),\n `Followed up - no signs` = sum(status_last_record == \"Seen_ok\" & date_last_record == database_date, na.rm=T),\n `Not Followed up` = sum(\n (status_last_record == \"NOT_ATTEMPTED\" | status_last_record == \"NOT_PERFORMED\") &\n date_last_record == database_date, na.rm=T)) %>% \n \n arrange(desc(`Registered contacts`))\n\n\n\n\n\n\n\nAnd now we apply styling from the formattable and knitr packages, including a footnote that shows the “as of” date.\n\ncontacts_table %>%\n mutate(\n `Admin 2` = formatter(\"span\", style = ~ formattable::style(\n color = ifelse(`Admin 2` == NA, \"red\", \"grey\"),\n font.weight = \"bold\",font.style = \"italic\"))(`Admin 2`),\n `Followed up - signs`= color_tile(\"white\", \"orange\")(`Followed up - signs`),\n `Followed up - no signs`= color_tile(\"white\", \"#A0E2BD\")(`Followed up - no signs`),\n `Became case`= color_tile(\"white\", \"grey\")(`Became case`),\n `Lost to follow up`= color_tile(\"white\", \"grey\")(`Lost to follow up`), \n `Never seen`= color_tile(\"white\", \"red\")(`Never seen`),\n `Active contacts` = color_tile(\"white\", \"#81A4CE\")(`Active contacts`)\n ) %>%\n kable(\"html\", escape = F, align =c(\"l\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\",\"c\")) %>%\n kable_styling(\"hover\", full_width = FALSE) %>%\n add_header_above(c(\" \" = 3, \n \"Of contacts currently under follow up\" = 5,\n \"Status of last visit\" = 3)) %>% \n kableExtra::footnote(general = str_glue(\"Data are current to {format(report_date, '%b %d %Y')}\"))\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOf contacts currently under follow up\n\n\nStatus of last visit\n\n\n\nAdmin 2\nRegistered contacts\nActive contacts\nIn first week\nIn second week\nBecame case\nLost to follow up\nNever seen\nFollowed up - signs\nFollowed up - no signs\nNot Followed up\n\n\n\n\nDjembe \n59\n30\n44\n0\n2\n15\n22\n0\n0\n0\n\n\nTrumpet\n3\n1\n3\n0\n0\n0\n0\n0\n0\n0\n\n\nVenu \n2\n0\n0\n0\n2\n0\n2\n0\n0\n0\n\n\nCongas \n1\n0\n0\n0\n1\n0\n1\n0\n0\n0\n\n\nCornet \n1\n0\n1\n0\n1\n0\n1\n0\n0\n0\n\n\n\nNote: \n\n\n\n\n\n\n\n\n\n\n\n\n Data are current to Jun 10 2020", "crumbs": [ "Analysis", "25  Contact tracing" @@ -2199,7 +2199,7 @@ "href": "new_pages/contact_tracing.html#transmission-matrices", "title": "25  Contact tracing", "section": "25.5 Transmission Matrices", - "text": "25.5 Transmission Matrices\nAs discussed in the Heat plots page, you can create a matrix of “who infected whom” using geom_tile().\nWhen new contacts are created, Go.Data stores this relationship information in the relationships API endpoint; and we can see the first 50 rows of this dataset below. This means that we can create a heat plot with relatively few steps given each contact is already joined to it’s source case.\n\n\n\n\n\n\nAs done above for the age pyramid comparing cases and contacts, we can select the few variables we need and create columns with categorical age groupings for both sources (cases) and targets (contacts).\n\nheatmap_ages <- relationships %>% \n select(source_age, target_age) %>% \n mutate( # transmute is like mutate() but removes all other columns\n source_age_class = epikit::age_categories(source_age, breakers = seq(0, 80, 5)),\n target_age_class = epikit::age_categories(target_age, breakers = seq(0, 80, 5))) \n\nAs described previously, we create cross-tabulation;\n\ncross_tab <- table(\n source_cases = heatmap_ages$source_age_class,\n target_cases = heatmap_ages$target_age_class)\n\ncross_tab\n\n target_cases\nsource_cases 0-4 5-9 10-14 15-19 20-24 25-29 30-34 35-39 40-44 45-49 50-54\n 0-4 0 0 0 0 0 0 0 0 0 1 0\n 5-9 0 0 1 0 0 0 0 1 0 0 0\n 10-14 0 0 0 0 0 0 0 0 0 0 0\n 15-19 0 0 0 0 0 0 0 0 0 0 0\n 20-24 1 1 0 1 2 0 2 1 0 0 0\n 25-29 1 2 0 0 0 0 0 0 0 0 0\n 30-34 0 0 0 0 0 0 0 0 1 1 0\n 35-39 0 2 0 0 0 0 0 0 0 1 0\n 40-44 0 0 0 0 1 0 2 1 0 3 1\n 45-49 1 2 2 0 0 0 3 0 1 0 3\n 50-54 1 2 1 2 0 0 1 0 0 3 4\n 55-59 0 1 0 0 1 1 2 0 0 0 0\n 60-64 0 0 0 0 0 0 0 0 0 0 0\n 65-69 0 0 0 0 0 0 0 0 0 0 0\n 70-74 0 0 0 0 0 0 0 0 0 0 0\n 75-79 0 0 0 0 0 0 0 0 0 0 0\n 80+ 1 0 0 2 1 0 0 0 1 0 0\n target_cases\nsource_cases 55-59 60-64 65-69 70-74 75-79 80+\n 0-4 1 0 0 0 0 0\n 5-9 1 0 0 0 0 0\n 10-14 0 0 0 0 0 0\n 15-19 0 0 0 0 0 0\n 20-24 1 0 0 0 0 1\n 25-29 0 0 0 0 0 0\n 30-34 1 0 0 0 0 0\n 35-39 0 0 0 0 0 0\n 40-44 1 0 0 0 1 1\n 45-49 2 1 0 0 0 1\n 50-54 1 0 1 0 0 1\n 55-59 0 0 0 0 0 0\n 60-64 0 0 0 0 0 0\n 65-69 0 0 0 0 0 0\n 70-74 0 0 0 0 0 0\n 75-79 0 0 0 0 0 0\n 80+ 0 0 0 0 0 0\n\n\nconvert into long format with proportions;\n\nlong_prop <- data.frame(prop.table(cross_tab))\n\nand create a heat-map for age.\n\nggplot(data = long_prop)+ # use long data, with proportions as Freq\n geom_tile( # visualize it in tiles\n aes(\n x = target_cases, # x-axis is case age\n y = source_cases, # y-axis is infector age\n fill = Freq))+ # color of the tile is the Freq column in the data\n scale_fill_gradient( # adjust the fill color of the tiles\n low = \"blue\",\n high = \"orange\")+\n theme(axis.text.x = element_text(angle = 90))+\n labs( # labels\n x = \"Target case age\",\n y = \"Source case age\",\n title = \"Who infected whom\",\n subtitle = \"Frequency matrix of transmission events\",\n fill = \"Proportion of all\\ntranmsission events\" # legend title\n )", + "text": "25.5 Transmission Matrices\nAs discussed in the Heat plots page, you can create a matrix of “who infected whom” using geom_tile().\nWhen new contacts are created, Go.Data stores this relationship information in the relationships API endpoint; and we can see the first 50 rows of this dataset below. This means that we can create a heat plot with relatively few steps given each contact is already joined to it’s source case.\n\n\n\n\n\n\nAs done above for the age pyramid comparing cases and contacts, we can select the few variables we need and create columns with categorical age groupings for both sources (cases) and targets (contacts).\n\nheatmap_ages <- relationships %>% \n select(source_age, target_age) %>% \n mutate( # transmute is like mutate() but removes all other columns\n source_age_class = epikit::age_categories(source_age, breakers = seq(0, 80, 5)),\n target_age_class = epikit::age_categories(target_age, breakers = seq(0, 80, 5))) \n\nAs described previously, we create cross-tabulation;\n\ncross_tab <- table(\n source_cases = heatmap_ages$source_age_class,\n target_cases = heatmap_ages$target_age_class)\n\ncross_tab\n\n target_cases\nsource_cases 0-4 5-9 10-14 15-19 20-24 25-29 30-34 35-39 40-44 45-49 50-54\n 0-4 0 0 0 0 0 0 0 0 0 1 0\n 5-9 0 0 1 0 0 0 0 1 0 0 0\n 10-14 0 0 0 0 0 0 0 0 0 0 0\n 15-19 0 0 0 0 0 0 0 0 0 0 0\n 20-24 1 1 0 1 2 0 2 1 0 0 0\n 25-29 1 2 0 0 0 0 0 0 0 0 0\n 30-34 0 0 0 0 0 0 0 0 1 1 0\n 35-39 0 2 0 0 0 0 0 0 0 1 0\n 40-44 0 0 0 0 1 0 2 1 0 3 1\n 45-49 1 2 2 0 0 0 3 0 1 0 3\n 50-54 1 2 1 2 0 0 1 0 0 3 4\n 55-59 0 1 0 0 1 1 2 0 0 0 0\n 60-64 0 0 0 0 0 0 0 0 0 0 0\n 65-69 0 0 0 0 0 0 0 0 0 0 0\n 70-74 0 0 0 0 0 0 0 0 0 0 0\n 75-79 0 0 0 0 0 0 0 0 0 0 0\n 80+ 1 0 0 2 1 0 0 0 1 0 0\n target_cases\nsource_cases 55-59 60-64 65-69 70-74 75-79 80+\n 0-4 1 0 0 0 0 0\n 5-9 1 0 0 0 0 0\n 10-14 0 0 0 0 0 0\n 15-19 0 0 0 0 0 0\n 20-24 1 0 0 0 0 1\n 25-29 0 0 0 0 0 0\n 30-34 1 0 0 0 0 0\n 35-39 0 0 0 0 0 0\n 40-44 1 0 0 0 1 1\n 45-49 2 1 0 0 0 1\n 50-54 1 0 1 0 0 1\n 55-59 0 0 0 0 0 0\n 60-64 0 0 0 0 0 0\n 65-69 0 0 0 0 0 0\n 70-74 0 0 0 0 0 0\n 75-79 0 0 0 0 0 0\n 80+ 0 0 0 0 0 0\n\n\nconvert into long format with proportions;\n\nlong_prop <- data.frame(prop.table(cross_tab))\n\nand create a heat-map for age.\n\nggplot(data = long_prop) + # use long data, with proportions as Freq\n geom_tile( # visualize it in tiles\n aes(\n x = target_cases, # x-axis is case age\n y = source_cases, # y-axis is infector age\n fill = Freq)) + # color of the tile is the Freq column in the data\n scale_fill_gradient( # adjust the fill color of the tiles\n low = \"blue\",\n high = \"orange\") +\n theme(axis.text.x = element_text(angle = 90)) +\n labs( # labels\n x = \"Target case age\",\n y = \"Source case age\",\n title = \"Who infected whom\",\n subtitle = \"Frequency matrix of transmission events\",\n fill = \"Proportion of all\\ntranmsission events\" # legend title\n )", "crumbs": [ "Analysis", "25  Contact tracing" @@ -2210,7 +2210,7 @@ "href": "new_pages/contact_tracing.html#resources", "title": "25  Contact tracing", "section": "25.6 Resources", - "text": "25.6 Resources\nhttps://github.com/WorldHealthOrganization/godata/tree/master/analytics/r-reporting\nhttps://worldhealthorganization.github.io/godata/\nhttps://community-godata.who.int/", + "text": "25.6 Resources\nGo.Data\nAutomated R Reporting using Go.Data API", "crumbs": [ "Analysis", "25  Contact tracing" @@ -2232,7 +2232,7 @@ "href": "new_pages/survey_analysis.html#overview", "title": "26  Survey analysis", "section": "", - "text": "Survey data\nObservation time\nWeighting\nSurvey design objects\nDescriptive analysis\nWeighted proportions\nWeighted rates", + "text": "Survey data.\nObservation time.\nWeighting.\nSurvey design objects.\nDescriptive analysis.\nWeighted proportions.\nWeighted rates.", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2243,7 +2243,7 @@ "href": "new_pages/survey_analysis.html#preparation", "title": "26  Survey analysis", "section": "26.2 Preparation", - "text": "26.2 Preparation\n\nPackages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\nHere we also demonstrate using the p_load_gh() function from pacman to install a load a package from github which has not yet been published on CRAN.\n\n## load packages from CRAN\npacman::p_load(rio, # File import\n here, # File locator\n tidyverse, # data management + ggplot2 graphics\n tsibble, # handle time series datasets\n survey, # for survey functions\n srvyr, # dplyr wrapper for survey package\n gtsummary, # wrapper for survey package to produce tables\n apyramid, # a package dedicated to creating age pyramids\n patchwork, # for combining ggplots\n ggforce # for alluvial/sankey plots\n ) \n\n## load packages from github\npacman::p_load_gh(\n \"R4EPI/sitrep\" # for observation time / weighting functions\n)\n\n\n\nLoad data\nThe example dataset used in this section:\n\nfictional mortality survey data.\nfictional population counts for the survey area.\ndata dictionary for the fictional mortality survey data.\n\nThis is based off the MSF OCA ethical review board pre-approved survey. The fictional dataset was produced as part of the “R4Epis” project. This is all based off data collected using KoboToolbox, which is a data collection software based off Open Data Kit.\nKobo allows you to export both the collected data, as well as the data dictionary for that dataset. We strongly recommend doing this as it simplifies data cleaning and is useful for looking up variables/questions.\nTIP: The Kobo data dictionary has variable names in the “name” column of the survey sheet. Possible values for each variable are specified in choices sheet. In the choices tab, “name” has the shortened value and the “label::english” and “label::french” columns have the appropriate long versions. Using the epidict package msf_dict_survey() function to import a Kobo dictionary excel file will re-format this for you so it can be used easily to recode. \nCAUTION: The example dataset is not the same as an export (as in Kobo you export different questionnaire levels individually) - see the survey data section below to merge the different levels.\nThe dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\n# import the survey data\nsurvey_data <- rio::import(\"survey_data.xlsx\")\n\n# import the dictionary into R\nsurvey_dict <- rio::import(\"survey_dict.xlsx\") \n\nThe first 10 rows of the survey are displayed below.\n\n\n\n\n\n\nWe also want to import the data on sampling population so that we can produce appropriate weights. This data can be in different formats, however we would suggest to have it as seen below (this can just be typed in to an excel).\n\n# import the population data\npopulation <- rio::import(\"population.xlsx\")\n\nThe first 10 rows of the survey are displayed below.\n\n\n\n\n\n\nFor cluster surveys you may want to add survey weights at the cluster level. You could read this data in as above. Alternatively if there are only a few counts, these could be entered as below in to a tibble. In any case you will need to have one column with a cluster identifier which matches your survey data, and another column with the number of households in each cluster.\n\n## define the number of households in each cluster\ncluster_counts <- tibble(cluster = c(\"village_1\", \"village_2\", \"village_3\", \"village_4\", \n \"village_5\", \"village_6\", \"village_7\", \"village_8\",\n \"village_9\", \"village_10\"), \n households = c(700, 400, 600, 500, 300, \n 800, 700, 400, 500, 500))\n\n\n\nClean data\nThe below makes sure that the date column is in the appropriate format. There are several other ways of doing this (see the Working with dates page for details), however using the dictionary to define dates is quick and easy.\nWe also create an age group variable using the age_categories() function from epikit - see cleaning data handbook section for details. In addition, we create a character variable defining which district the various clusters are in.\nFinally, we recode all of the yes/no variables to TRUE/FALSE variables - otherwise these cant be used by the survey proportion functions.\n\n## select the date variable names from the dictionary \nDATEVARS <- survey_dict %>% \n filter(type == \"date\") %>% \n filter(name %in% names(survey_data)) %>% \n ## filter to match the column names of your data\n pull(name) # select date vars\n \n## change to dates \nsurvey_data <- survey_data %>%\n mutate(across(all_of(DATEVARS), as.Date))\n\n\n## add those with only age in months to the year variable (divide by twelve)\nsurvey_data <- survey_data %>% \n mutate(age_years = if_else(is.na(age_years), \n age_months / 12, \n age_years))\n\n## define age group variable\nsurvey_data <- survey_data %>% \n mutate(age_group = age_categories(age_years, \n breakers = c(0, 3, 15, 30, 45)\n ))\n\n\n## create a character variable based off groups of a different variable \nsurvey_data <- survey_data %>% \n mutate(health_district = case_when(\n cluster_number %in% c(1:5) ~ \"district_a\", \n TRUE ~ \"district_b\"\n ))\n\n\n## select the yes/no variable names from the dictionary \nYNVARS <- survey_dict %>% \n filter(type == \"yn\") %>% \n filter(name %in% names(survey_data)) %>% \n ## filter to match the column names of your data\n pull(name) # select yn vars\n \n## change to dates \nsurvey_data <- survey_data %>%\n mutate(across(all_of(YNVARS), \n str_detect, \n pattern = \"yes\"))\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `across(all_of(YNVARS), str_detect, pattern = \"yes\")`.\nCaused by warning:\n! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.\nSupply arguments directly to `.fns` through an anonymous function instead.\n\n # Previously\n across(a:b, mean, na.rm = TRUE)\n\n # Now\n across(a:b, \\(x) mean(x, na.rm = TRUE))", + "text": "26.2 Preparation\n\nPackages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\nHere we also demonstrate using the p_load_gh() function from pacman to install a load a package from github which has not yet been published on CRAN.\n\n## load packages from CRAN\npacman::p_load(rio, # File import\n here, # File locator\n tidyverse, # data management + ggplot2 graphics\n tsibble, # handle time series datasets\n survey, # for survey functions\n srvyr, # dplyr wrapper for survey package\n gtsummary, # wrapper for survey package to produce tables\n apyramid, # a package dedicated to creating age pyramids\n patchwork, # for combining ggplots\n ggforce # for alluvial/sankey plots\n ) \n\n## load packages from github\npacman::p_load_gh(\n \"R4EPI/sitrep\" # for observation time / weighting functions\n)\n\n\n\nLoad data\nThe example dataset used in this section:\n\nFictional mortality survey data.\nFictional population counts for the survey area.\nData dictionary for the fictional mortality survey data.\n\nThis is based off the MSF OCA ethical review board pre-approved survey. The fictional dataset was produced as part of the “R4Epis” project. This is all based off data collected using KoboToolbox, which is a data collection software based off Open Data Kit.\nKobo allows you to export both the collected data, as well as the data dictionary for that dataset. We strongly recommend doing this as it simplifies data cleaning and is useful for looking up variables/questions.\nTIP: The Kobo data dictionary has variable names in the “name” column of the survey sheet. Possible values for each variable are specified in choices sheet. In the choices tab, “name” has the shortened value and the “label::english” and “label::french” columns have the appropriate long versions. Using the epidict package msf_dict_survey() function to import a Kobo dictionary excel file will re-format this for you so it can be used easily to recode. \nCAUTION: The example dataset is not the same as an export (as in Kobo you export different questionnaire levels individually) - see the survey data section below to merge the different levels.\nThe dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\n# import the survey data\nsurvey_data <- rio::import(\"survey_data.xlsx\")\n\n# import the dictionary into R\nsurvey_dict <- rio::import(\"survey_dict.xlsx\") \n\nThe first 10 rows of the survey are displayed below.\n\n\n\n\n\n\nWe also want to import the data on sampling population so that we can produce appropriate weights. This data can be in different formats, however we would suggest to have it as seen below (this can just be typed in to an excel).\n\n# import the population data\npopulation <- rio::import(\"population.xlsx\")\n\nThe first 10 rows of the survey are displayed below.\n\n\n\n\n\n\nFor cluster surveys you may want to add survey weights at the cluster level. You could read this data in as above. Alternatively if there are only a few counts, these could be entered as below in to a tibble. In any case you will need to have one column with a cluster identifier which matches your survey data, and another column with the number of households in each cluster.\n\n## define the number of households in each cluster\ncluster_counts <- tibble(cluster = c(\"village_1\", \"village_2\", \"village_3\", \"village_4\", \n \"village_5\", \"village_6\", \"village_7\", \"village_8\",\n \"village_9\", \"village_10\"), \n households = c(700, 400, 600, 500, 300, \n 800, 700, 400, 500, 500))\n\n\n\nClean data\nThe below makes sure that the date column is in the appropriate format. There are several other ways of doing this (see the Working with dates page for details), however using the dictionary to define dates is quick and easy.\nWe also create an age group variable using the age_categories() function from epikit - see cleaning data handbook section for details. In addition, we create a character variable defining which district the various clusters are in.\nFinally, we recode all of the yes/no variables to TRUE/FALSE variables - otherwise these cant be used by the survey proportion functions.\n\n## select the date variable names from the dictionary \nDATEVARS <- survey_dict %>% \n filter(type == \"date\") %>% \n filter(name %in% names(survey_data)) %>% \n ## filter to match the column names of your data\n pull(name) # select date vars\n \n## change to dates \nsurvey_data <- survey_data %>%\n mutate(across(all_of(DATEVARS), as.Date))\n\n\n## add those with only age in months to the year variable (divide by twelve)\nsurvey_data <- survey_data %>% \n mutate(age_years = if_else(is.na(age_years), \n age_months / 12, \n age_years))\n\n## define age group variable\nsurvey_data <- survey_data %>% \n mutate(age_group = age_categories(age_years, \n breakers = c(0, 3, 15, 30, 45)\n ))\n\n\n## create a character variable based off groups of a different variable \nsurvey_data <- survey_data %>% \n mutate(health_district = case_when(\n cluster_number %in% c(1:5) ~ \"district_a\", \n TRUE ~ \"district_b\"\n ))\n\n\n## select the yes/no variable names from the dictionary \nYNVARS <- survey_dict %>% \n filter(type == \"yn\") %>% \n filter(name %in% names(survey_data)) %>% \n ## filter to match the column names of your data\n pull(name) # select yn vars\n \n## change to dates \nsurvey_data <- survey_data %>%\n mutate(across(all_of(YNVARS), \n str_detect, \n pattern = \"yes\"))", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2254,7 +2254,7 @@ "href": "new_pages/survey_analysis.html#survey-data", "title": "26  Survey analysis", "section": "26.3 Survey data", - "text": "26.3 Survey data\nThere numerous different sampling designs that can be used for surveys. Here we will demonstrate code for: - Stratified - Cluster - Stratified and cluster\nAs described above (depending on how you design your questionnaire) the data for each level would be exported as a separate dataset from Kobo. In our example there is one level for households and one level for individuals within those households.\nThese two levels are linked by a unique identifier. For a Kobo dataset this variable is “_index” at the household level, which matches the “_parent_index” at the individual level. This will create new rows for household with each matching individual, see the handbook section on joining for details.\n\n## join the individual and household data to form a complete data set\nsurvey_data <- left_join(survey_data_hh, \n survey_data_indiv,\n by = c(\"_index\" = \"_parent_index\"))\n\n\n## create a unique identifier by combining indeces of the two levels \nsurvey_data <- survey_data %>% \n mutate(uid = str_glue(\"{index}_{index_y}\"))", + "text": "26.3 Survey data\nThere numerous different sampling designs that can be used for surveys. Here we will demonstrate code for: - Stratified - Cluster - Stratified and cluster", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2265,7 +2265,7 @@ "href": "new_pages/survey_analysis.html#observation-time", "title": "26  Survey analysis", "section": "26.4 Observation time", - "text": "26.4 Observation time\nFor mortality surveys we want to now how long each individual was present for in the location to be able to calculate an appropriate mortality rate for our period of interest. This is not relevant to all surveys, but particularly for mortality surveys this is important as they are conducted frequently among mobile or displaced populations.\nTo do this we first define our time period of interest, also known as a recall period (i.e. the time that participants are asked to report on when answering questions). We can then use this period to set inappropriate dates to missing, i.e. if deaths are reported from outside the period of interest.\n\n## set the start/end of recall period\n## can be changed to date variables from dataset \n## (e.g. arrival date & date questionnaire)\nsurvey_data <- survey_data %>% \n mutate(recall_start = as.Date(\"2018-01-01\"), \n recall_end = as.Date(\"2018-05-01\")\n )\n\n\n# set inappropriate dates to NA based on rules \n## e.g. arrivals before start, departures departures after end\nsurvey_data <- survey_data %>%\n mutate(\n arrived_date = if_else(arrived_date < recall_start, \n as.Date(NA),\n arrived_date),\n birthday_date = if_else(birthday_date < recall_start,\n as.Date(NA),\n birthday_date),\n left_date = if_else(left_date > recall_end,\n as.Date(NA),\n left_date),\n death_date = if_else(death_date > recall_end,\n as.Date(NA),\n death_date)\n )\n\nWe can then use our date variables to define start and end dates for each individual. We can use the find_start_date() function from sitrep to fine the causes for the dates and then use that to calculate the difference between days (person-time).\nstart date: Earliest appropriate arrival event within your recall period Either the beginning of your recall period (which you define in advance), or a date after the start of recall if applicable (e.g. arrivals or births)\nend date: Earliest appropriate departure event within your recall period Either the end of your recall period, or a date before the end of recall if applicable (e.g. departures, deaths)\n\n## create new variables for start and end dates/causes\nsurvey_data <- survey_data %>% \n ## choose earliest date entered in survey\n ## from births, household arrivals, and camp arrivals \n find_start_date(\"birthday_date\",\n \"arrived_date\",\n period_start = \"recall_start\",\n period_end = \"recall_end\",\n datecol = \"startdate\",\n datereason = \"startcause\" \n ) %>%\n ## choose earliest date entered in survey\n ## from camp departures, death and end of the study\n find_end_date(\"left_date\",\n \"death_date\",\n period_start = \"recall_start\",\n period_end = \"recall_end\",\n datecol = \"enddate\",\n datereason = \"endcause\" \n )\n\n\n## label those that were present at the start/end (except births/deaths)\nsurvey_data <- survey_data %>% \n mutate(\n ## fill in start date to be the beginning of recall period (for those empty) \n startdate = if_else(is.na(startdate), recall_start, startdate), \n ## set the start cause to present at start if equal to recall period \n ## unless it is equal to the birth date \n startcause = if_else(startdate == recall_start & startcause != \"birthday_date\",\n \"Present at start\", startcause), \n ## fill in end date to be end of recall period (for those empty) \n enddate = if_else(is.na(enddate), recall_end, enddate), \n ## set the end cause to present at end if equall to recall end \n ## unless it is equal to the death date\n endcause = if_else(enddate == recall_end & endcause != \"death_date\", \n \"Present at end\", endcause))\n\n\n## Define observation time in days\nsurvey_data <- survey_data %>% \n mutate(obstime = as.numeric(enddate - startdate))", + "text": "26.4 Observation time\nFor mortality surveys we want to now how long each individual was present for in the location to be able to calculate an appropriate mortality rate for our period of interest. This is not relevant to all surveys, but particularly for mortality surveys this is important as they are conducted frequently among mobile or displaced populations.\nTo do this we first define our time period of interest, also known as a recall period (i.e. the time that participants are asked to report on when answering questions). We can then use this period to set inappropriate dates to missing, i.e. if deaths are reported from outside the period of interest.\n\n## set the start/end of recall period\n## can be changed to date variables from dataset \n## (e.g. arrival date & date questionnaire)\nsurvey_data <- survey_data %>% \n mutate(recall_start = as.Date(\"2018-01-01\"), \n recall_end = as.Date(\"2018-05-01\")\n )\n\n\n# set inappropriate dates to NA based on rules \n## e.g. arrivals before start, departures departures after end\nsurvey_data <- survey_data %>%\n mutate(\n arrived_date = if_else(arrived_date < recall_start, \n as.Date(NA),\n arrived_date),\n birthday_date = if_else(birthday_date < recall_start,\n as.Date(NA),\n birthday_date),\n left_date = if_else(left_date > recall_end,\n as.Date(NA),\n left_date),\n death_date = if_else(death_date > recall_end,\n as.Date(NA),\n death_date)\n )\n\nWe can then use our date variables to define start and end dates for each individual. We can use the find_start_date() function from sitrep to fine the causes for the dates and then use that to calculate the difference between days (person-time).\nstart date: Earliest appropriate arrival event within your recall period. Either the beginning of your recall period (which you define in advance), or a date after the start of recall if applicable (e.g. arrivals or births).\nend date: Earliest appropriate departure event within your recall period. Either the end of your recall period, or a date before the end of recall if applicable (e.g. departures, deaths).\n\n## create new variables for start and end dates/causes\nsurvey_data <- survey_data %>% \n ## choose earliest date entered in survey\n ## from births, household arrivals, and camp arrivals \n find_start_date(\"birthday_date\",\n \"arrived_date\",\n period_start = \"recall_start\",\n period_end = \"recall_end\",\n datecol = \"startdate\",\n datereason = \"startcause\" \n ) %>%\n ## choose earliest date entered in survey\n ## from camp departures, death and end of the study\n find_end_date(\"left_date\",\n \"death_date\",\n period_start = \"recall_start\",\n period_end = \"recall_end\",\n datecol = \"enddate\",\n datereason = \"endcause\" \n )\n\n\n## label those that were present at the start/end (except births/deaths)\nsurvey_data <- survey_data %>% \n mutate(\n ## fill in start date to be the beginning of recall period (for those empty) \n startdate = if_else(is.na(startdate), recall_start, startdate), \n ## set the start cause to present at start if equal to recall period \n ## unless it is equal to the birth date \n startcause = if_else(startdate == recall_start & startcause != \"birthday_date\",\n \"Present at start\", startcause), \n ## fill in end date to be end of recall period (for those empty) \n enddate = if_else(is.na(enddate), recall_end, enddate), \n ## set the end cause to present at end if equall to recall end \n ## unless it is equal to the death date\n endcause = if_else(enddate == recall_end & endcause != \"death_date\", \n \"Present at end\", endcause))\n\n\n## Define observation time in days\nsurvey_data <- survey_data %>% \n mutate(obstime = as.numeric(enddate - startdate))", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2287,7 +2287,7 @@ "href": "new_pages/survey_analysis.html#survey-design-objects", "title": "26  Survey analysis", "section": "26.6 Survey design objects", - "text": "26.6 Survey design objects\nCreate survey object according to your study design. Used the same way as data frames to calculate weight proportions etc. Make sure that all necessary variables are created before this.\nThere are four options, comment out those you do not use: - Simple random - Stratified - Cluster - Stratified cluster\nFor this template - we will pretend that we cluster surveys in two separate strata (health districts A and B). So to get overall estimates we need have combined cluster and strata weights.\nAs mentioned previously, there are two packages available for doing this. The classic one is survey and then there is a wrapper package called srvyr that makes tidyverse-friendly objects and functions. We will demonstrate both, but note that most of the code in this chapter will use srvyr based objects. The one exception is that the gtsummary package only accepts survey objects.\n\n26.6.1 Survey package\nThe survey package effectively uses base R coding, and so it is not possible to use pipes (%>%) or other dplyr syntax. With the survey package we use the svydesign() function to define a survey object with appropriate clusters, weights and strata.\nNOTE: we need to use the tilde (~) in front of variables, this is because the package uses the base R syntax of assigning variables based on formulae. \n\n# simple random ---------------------------------------------------------------\nbase_survey_design_simple <- svydesign(ids = ~1, # 1 for no cluster ids\n weights = NULL, # No weight added\n strata = NULL, # sampling was simple (no strata)\n data = survey_data # have to specify the dataset\n )\n\n## stratified ------------------------------------------------------------------\nbase_survey_design_strata <- svydesign(ids = ~1, # 1 for no cluster ids\n weights = ~surv_weight_strata, # weight variable created above\n strata = ~health_district, # sampling was stratified by district\n data = survey_data # have to specify the dataset\n )\n\n# cluster ---------------------------------------------------------------------\nbase_survey_design_cluster <- svydesign(ids = ~village_name, # cluster ids\n weights = ~surv_weight_cluster, # weight variable created above\n strata = NULL, # sampling was simple (no strata)\n data = survey_data # have to specify the dataset\n )\n\n# stratified cluster ----------------------------------------------------------\nbase_survey_design <- svydesign(ids = ~village_name, # cluster ids\n weights = ~surv_weight_cluster_strata, # weight variable created above\n strata = ~health_district, # sampling was stratified by district\n data = survey_data # have to specify the dataset\n )\n\n\n\n26.6.2 Srvyr package\nWith the srvyr package we can use the as_survey_design() function, which has all the same arguments as above but allows pipes (%>%), and so we do not need to use the tilde (~).\n\n## simple random ---------------------------------------------------------------\nsurvey_design_simple <- survey_data %>% \n as_survey_design(ids = 1, # 1 for no cluster ids \n weights = NULL, # No weight added\n strata = NULL # sampling was simple (no strata)\n )\n## stratified ------------------------------------------------------------------\nsurvey_design_strata <- survey_data %>%\n as_survey_design(ids = 1, # 1 for no cluster ids\n weights = surv_weight_strata, # weight variable created above\n strata = health_district # sampling was stratified by district\n )\n## cluster ---------------------------------------------------------------------\nsurvey_design_cluster <- survey_data %>%\n as_survey_design(ids = village_name, # cluster ids\n weights = surv_weight_cluster, # weight variable created above\n strata = NULL # sampling was simple (no strata)\n )\n\n## stratified cluster ----------------------------------------------------------\nsurvey_design <- survey_data %>%\n as_survey_design(ids = village_name, # cluster ids\n weights = surv_weight_cluster_strata, # weight variable created above\n strata = health_district # sampling was stratified by district\n )", + "text": "26.6 Survey design objects\nNext you should create a survey object according to your study design. This is used in the same way as data frames to calculate weight proportions and other aspects of survey analysis. You should make sure all your necessary variables are created before this.\nThere are four options: - Simple random - Stratified - Cluster - Stratified cluster\nIn the template below - we will pretend that we cluster surveys in two separate strata (health districts A and B).\nTo get overall estimates we need to have combined cluster and strata weights.\nAs mentioned previously, there are two packages available for doing this. The classic one is survey and then there is a wrapper package called srvyr that makes tidyverse-friendly objects and functions. We will demonstrate both, but note that most of the code in this chapter will use srvyr based objects. The one exception is that the gtsummary package only accepts survey objects.\n\n26.6.1 Survey package\nThe survey package effectively uses base R coding, and so it is not possible to use pipes (%>%) or other dplyr syntax. With the survey package we use the svydesign() function to define a survey object with appropriate clusters, weights and strata.\nNOTE: we need to use the tilde (~) in front of variables, this is because the package uses the base R syntax of assigning variables based on formulae. \n\n# simple random ---------------------------------------------------------------\nbase_survey_design_simple <- svydesign(ids = ~1, # 1 for no cluster ids\n weights = NULL, # No weight added\n strata = NULL, # sampling was simple (no strata)\n data = survey_data # have to specify the dataset\n )\n\n## stratified ------------------------------------------------------------------\nbase_survey_design_strata <- svydesign(ids = ~1, # 1 for no cluster ids\n weights = ~surv_weight_strata, # weight variable created above\n strata = ~health_district, # sampling was stratified by district\n data = survey_data # have to specify the dataset\n )\n\n# cluster ---------------------------------------------------------------------\nbase_survey_design_cluster <- svydesign(ids = ~village_name, # cluster ids\n weights = ~surv_weight_cluster, # weight variable created above\n strata = NULL, # sampling was simple (no strata)\n data = survey_data # have to specify the dataset\n )\n\n# stratified cluster ----------------------------------------------------------\nbase_survey_design <- svydesign(ids = ~village_name, # cluster ids\n weights = ~surv_weight_cluster_strata, # weight variable created above\n strata = ~health_district, # sampling was stratified by district\n data = survey_data # have to specify the dataset\n )\n\n\n\n26.6.2 Srvyr package\nWith the srvyr package we can use the as_survey_design() function, which has all the same arguments as above but allows pipes (%>%), and so we do not need to use the tilde (~).\n\n## simple random ---------------------------------------------------------------\nsurvey_design_simple <- survey_data %>% \n as_survey_design(ids = 1, # 1 for no cluster ids \n weights = NULL, # No weight added\n strata = NULL # sampling was simple (no strata)\n )\n## stratified ------------------------------------------------------------------\nsurvey_design_strata <- survey_data %>%\n as_survey_design(ids = 1, # 1 for no cluster ids\n weights = surv_weight_strata, # weight variable created above\n strata = health_district # sampling was stratified by district\n )\n## cluster ---------------------------------------------------------------------\nsurvey_design_cluster <- survey_data %>%\n as_survey_design(ids = village_name, # cluster ids\n weights = surv_weight_cluster, # weight variable created above\n strata = NULL # sampling was simple (no strata)\n )\n\n## stratified cluster ----------------------------------------------------------\nsurvey_design <- survey_data %>%\n as_survey_design(ids = village_name, # cluster ids\n weights = surv_weight_cluster_strata, # weight variable created above\n strata = health_district # sampling was stratified by district\n )", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2298,7 +2298,7 @@ "href": "new_pages/survey_analysis.html#descriptive-analysis", "title": "26  Survey analysis", "section": "26.7 Descriptive analysis", - "text": "26.7 Descriptive analysis\nBasic descriptive analysis and visualisation is covered extensively in other chapters of the handbook, so we will not dwell on it here. For details see the chapters on descriptive tables, statistical tests, tables for presentation, ggplot basics and R markdown reports.\nIn this section we will focus on how to investigate bias in your sample and visualise this. We will also look at visualising population flow in a survey setting using alluvial/sankey diagrams.\nIn general, you should consider including the following descriptive analyses:\n\nFinal number of clusters, households and individuals included\n\nNumber of excluded individuals and the reasons for exclusion\nMedian (range) number of households per cluster and individuals per household\n\n\n26.7.1 Sampling bias\nCompare the proportions in each age group between your sample and the source population. This is important to be able to highlight potential sampling bias. You could similarly repeat this looking at distributions by sex.\nNote that these p-values are just indicative, and a descriptive discussion (or visualisation with age-pyramids below) of the distributions in your study sample compared to the source population is more important than the binomial test itself. This is because increasing sample size will more often than not lead to differences that may be irrelevant after weighting your data.\n\n## counts and props of the study population\nag <- survey_data %>% \n group_by(age_group) %>% \n drop_na(age_group) %>% \n tally() %>% \n mutate(proportion = n / sum(n), \n n_total = sum(n))\n\n## counts and props of the source population\npropcount <- population %>% \n group_by(age_group) %>%\n tally(population) %>%\n mutate(proportion = n / sum(n))\n\n## bind together the columns of two tables, group by age, and perform a \n## binomial test to see if n/total is significantly different from population\n## proportion.\n ## suffix here adds to text to the end of columns in each of the two datasets\nleft_join(ag, propcount, by = \"age_group\", suffix = c(\"\", \"_pop\")) %>%\n group_by(age_group) %>%\n ## broom::tidy(binom.test()) makes a data frame out of the binomial test and\n ## will add the variables p.value, parameter, conf.low, conf.high, method, and\n ## alternative. We will only use p.value here. You can include other\n ## columns if you want to report confidence intervals\n mutate(binom = list(broom::tidy(binom.test(n, n_total, proportion_pop)))) %>%\n unnest(cols = c(binom)) %>% # important for expanding the binom.test data frame\n mutate(proportion_pop = proportion_pop * 100) %>%\n ## Adjusting the p-values to correct for false positives \n ## (because testing multiple age groups). This will only make \n ## a difference if you have many age categories\n mutate(p.value = p.adjust(p.value, method = \"holm\")) %>%\n \n ## Only show p-values over 0.001 (those under report as <0.001)\n mutate(p.value = ifelse(p.value < 0.001, \n \"<0.001\", \n as.character(round(p.value, 3)))) %>% \n \n ## rename the columns appropriately\n select(\n \"Age group\" = age_group,\n \"Study population (n)\" = n,\n \"Study population (%)\" = proportion,\n \"Source population (n)\" = n_pop,\n \"Source population (%)\" = proportion_pop,\n \"P-value\" = p.value\n )\n\n# A tibble: 5 × 6\n# Groups: Age group [5]\n `Age group` `Study population (n)` `Study population (%)`\n <chr> <int> <dbl>\n1 0-2 12 0.0256\n2 3-14 42 0.0896\n3 15-29 64 0.136 \n4 30-44 52 0.111 \n5 45+ 299 0.638 \n# ℹ 3 more variables: `Source population (n)` <dbl>,\n# `Source population (%)` <dbl>, `P-value` <chr>\n\n\n\n\n26.7.2 Demographic pyramids\nDemographic (or age-sex) pyramids are an easy way of visualising the distribution in your survey population. It is also worth considering creating descriptive tables of age and sex by survey strata. We will demonstrate using the apyramid package as it allows for weighted proportions using our survey design object created above. Other options for creating demographic pyramids are covered extensively in that chapter of the handbook. We will also use a wrapper function from apyramid called age_pyramid() which saves a few lines of coding for producing a plot with proportions.\nAs with the formal binomial test of difference, seen above in the sampling bias section, we are interested here in visualising whether our sampled population is substantially different from the source population and whether weighting corrects this difference. To do this we will use the patchwork package to show our ggplot visualisations side-by-side; for details see the section on combining plots in ggplot tips chapter of the handbook. We will visualise our source population, our un-weighted survey population and our weighted survey population. You may also consider visualising by each strata of your survey - in our example here that would be by using the argument stack_by = \"health_district\" (see ?plot_age_pyramid for details).\nNOTE: The x and y axes are flipped in pyramids \n\n## define x-axis limits and labels ---------------------------------------------\n## (update these numbers to be the values for your graph)\nmax_prop <- 35 # choose the highest proportion you want to show \nstep <- 5 # choose the space you want beween labels \n\n## this part defines vector using the above numbers with axis breaks\nbreaks <- c(\n seq(max_prop/100 * -1, 0 - step/100, step/100), \n 0, \n seq(0 + step / 100, max_prop/100, step/100)\n )\n\n## this part defines vector using the above numbers with axis limits\nlimits <- c(max_prop/100 * -1, max_prop/100)\n\n## this part defines vector using the above numbers with axis labels\nlabels <- c(\n seq(max_prop, step, -step), \n 0, \n seq(step, max_prop, step)\n )\n\n\n## create plots individually --------------------------------------------------\n\n## plot the source population \n## nb: this needs to be collapsed for the overall population (i.e. removing health districts)\nsource_population <- population %>%\n ## ensure that age and sex are factors\n mutate(age_group = factor(age_group, \n levels = c(\"0-2\", \n \"3-14\", \n \"15-29\",\n \"30-44\", \n \"45+\")), \n sex = factor(sex)) %>% \n group_by(age_group, sex) %>% \n ## add the counts for each health district together \n summarise(population = sum(population)) %>% \n ## remove the grouping so can calculate overall proportion\n ungroup() %>% \n mutate(proportion = population / sum(population)) %>% \n ## plot pyramid \n age_pyramid(\n age_group = age_group, \n split_by = sex, \n count = proportion, \n proportional = TRUE) +\n ## only show the y axis label (otherwise repeated in all three plots)\n labs(title = \"Source population\", \n y = \"\", \n x = \"Age group (years)\") + \n ## make the x axis the same for all plots \n scale_y_continuous(breaks = breaks, \n limits = limits, \n labels = labels)\n \n \n## plot the unweighted sample population \nsample_population <- age_pyramid(survey_data, \n age_group = \"age_group\", \n split_by = \"sex\",\n proportion = TRUE) + \n ## only show the x axis label (otherwise repeated in all three plots)\n labs(title = \"Unweighted sample population\", \n y = \"Proportion (%)\", \n x = \"\") + \n ## make the x axis the same for all plots \n scale_y_continuous(breaks = breaks, \n limits = limits, \n labels = labels)\n\n\n## plot the weighted sample population \nweighted_population <- survey_design %>% \n ## make sure the variables are factors\n mutate(age_group = factor(age_group), \n sex = factor(sex)) %>%\n age_pyramid(\n age_group = \"age_group\",\n split_by = \"sex\", \n proportion = TRUE) +\n ## only show the x axis label (otherwise repeated in all three plots)\n labs(title = \"Weighted sample population\", \n y = \"\", \n x = \"\") + \n ## make the x axis the same for all plots \n scale_y_continuous(breaks = breaks, \n limits = limits, \n labels = labels)\n\n## combine all three plots ----------------------------------------------------\n## combine three plots next to eachother using + \nsource_population + sample_population + weighted_population + \n ## only show one legend and define theme \n ## note the use of & for combining theme with plot_layout()\n plot_layout(guides = \"collect\") & \n theme(legend.position = \"bottom\", # move legend to bottom\n legend.title = element_blank(), # remove title\n text = element_text(size = 18), # change text size\n axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1) # turn x-axis text\n )\n\n\n\n\n\n\n\n\n\n\n26.7.3 Alluvial/sankey diagram\nVisualising starting points and outcomes for individuals can be very helpful to get an overview. There is quite an obvious application for mobile populations, however there are numerous other applications such as cohorts or any other situation where there are transitions in states for individuals. These diagrams have several different names including alluvial, sankey and parallel sets - the details are in the handbook chapter on diagrams and charts.\n\n## summarize data\nflow_table <- survey_data %>%\n count(startcause, endcause, sex) %>% # get counts \n gather_set_data(x = c(\"startcause\", \"endcause\")) # change format for plotting\n\n\n## plot your dataset \n ## on the x axis is the start and end causes\n ## gather_set_data generates an ID for each possible combination\n ## splitting by y gives the possible start/end combos\n ## value as n gives it as counts (could also be changed to proportion)\nggplot(flow_table, aes(x, id = id, split = y, value = n)) +\n ## colour lines by sex \n geom_parallel_sets(aes(fill = sex), alpha = 0.5, axis.width = 0.2) +\n ## fill in the label boxes grey\n geom_parallel_sets_axes(axis.width = 0.15, fill = \"grey80\", color = \"grey80\") +\n ## change text colour and angle (needs to be adjusted)\n geom_parallel_sets_labels(color = \"black\", angle = 0, size = 5) +\n ## remove axis labels\n theme_void()+\n ## move legend to bottom\n theme(legend.position = \"bottom\")", + "text": "26.7 Descriptive analysis\nBasic descriptive analysis and visualisation is covered extensively in other chapters of the handbook, so we will not dwell on it here. For details see the chapters on descriptive tables, statistical tests, tables for presentation, ggplot basics and R markdown reports.\nIn this section we will focus on how to investigate bias in your sample and visualise this. We will also look at visualising population flow in a survey setting using alluvial/sankey diagrams.\nIn general, you should consider including the following descriptive analyses:\n\nFinal number of clusters, households and individuals included.\n\nNumber of excluded individuals and the reasons for exclusion.\nMedian (range) number of households per cluster and individuals per household.\n\n\n26.7.1 Sampling bias\nCompare the proportions in each age group between your sample and the source population. This is important to be able to highlight potential sampling bias. You could similarly repeat this looking at distributions by sex.\nNote that these p-values are just indicative, and a descriptive discussion (or visualisation with age-pyramids below) of the distributions in your study sample compared to the source population is more important than the binomial test itself. This is because increasing sample size will more often than not lead to differences that may be irrelevant after weighting your data.\n\n## counts and props of the study population\nag <- survey_data %>% \n group_by(age_group) %>% \n drop_na(age_group) %>% \n tally() %>% \n mutate(proportion = n / sum(n), \n n_total = sum(n))\n\n## counts and props of the source population\npropcount <- population %>% \n group_by(age_group) %>%\n tally(population) %>%\n mutate(proportion = n / sum(n))\n\n## bind together the columns of two tables, group by age, and perform a \n## binomial test to see if n/total is significantly different from population\n## proportion.\n ## suffix here adds to text to the end of columns in each of the two datasets\nleft_join(ag, propcount, by = \"age_group\", suffix = c(\"\", \"_pop\")) %>%\n group_by(age_group) %>%\n ## broom::tidy(binom.test()) makes a data frame out of the binomial test and\n ## will add the variables p.value, parameter, conf.low, conf.high, method, and\n ## alternative. We will only use p.value here. You can include other\n ## columns if you want to report confidence intervals\n mutate(binom = list(broom::tidy(binom.test(n, n_total, proportion_pop)))) %>%\n unnest(cols = c(binom)) %>% # important for expanding the binom.test data frame\n mutate(proportion_pop = proportion_pop * 100) %>%\n ## Adjusting the p-values to correct for false positives \n ## (because testing multiple age groups). This will only make \n ## a difference if you have many age categories\n mutate(p.value = p.adjust(p.value, method = \"holm\")) %>%\n \n ## Only show p-values over 0.001 (those under report as <0.001)\n mutate(p.value = ifelse(p.value < 0.001, \n \"<0.001\", \n as.character(round(p.value, 3)))) %>% \n \n ## rename the columns appropriately\n select(\n \"Age group\" = age_group,\n \"Study population (n)\" = n,\n \"Study population (%)\" = proportion,\n \"Source population (n)\" = n_pop,\n \"Source population (%)\" = proportion_pop,\n \"P-value\" = p.value\n )\n\n# A tibble: 5 × 6\n# Groups: Age group [5]\n `Age group` `Study population (n)` `Study population (%)`\n <chr> <int> <dbl>\n1 0-2 12 0.0256\n2 3-14 42 0.0896\n3 15-29 64 0.136 \n4 30-44 52 0.111 \n5 45+ 299 0.638 \n# ℹ 3 more variables: `Source population (n)` <dbl>,\n# `Source population (%)` <dbl>, `P-value` <chr>\n\n\n\n\n26.7.2 Demographic pyramids\nDemographic (or age-sex) pyramids are an easy way of visualising the distribution in your survey population. It is also worth considering creating descriptive tables of age and sex by survey strata. We will demonstrate using the apyramid package as it allows for weighted proportions using our survey design object created above. Other options for creating demographic pyramids are covered extensively in that chapter of the handbook. We will also use a wrapper function from apyramid called age_pyramid() which saves a few lines of coding for producing a plot with proportions.\nAs with the formal binomial test of difference, seen above in the sampling bias section, we are interested here in visualising whether our sampled population is substantially different from the source population and whether weighting corrects this difference. To do this we will use the patchwork package to show our ggplot visualisations side-by-side; for details see the section on combining plots in ggplot tips chapter of the handbook. We will visualise our source population, our un-weighted survey population and our weighted survey population. You may also consider visualising by each strata of your survey - in our example here that would be by using the argument stack_by = \"health_district\" (see ?plot_age_pyramid for details).\nNOTE: The x and y axes are flipped in pyramids \n\n## define x-axis limits and labels ---------------------------------------------\n## (update these numbers to be the values for your graph)\nmax_prop <- 35 # choose the highest proportion you want to show \nstep <- 5 # choose the space you want beween labels \n\n## this part defines vector using the above numbers with axis breaks\nbreaks <- c(\n seq(max_prop/100 * -1, 0 - step/100, step/100), \n 0, \n seq(0 + step / 100, max_prop/100, step/100)\n )\n\n## this part defines vector using the above numbers with axis limits\nlimits <- c(max_prop/100 * -1, max_prop/100)\n\n## this part defines vector using the above numbers with axis labels\nlabels <- c(\n seq(max_prop, step, -step), \n 0, \n seq(step, max_prop, step)\n )\n\n\n## create plots individually --------------------------------------------------\n\n## plot the source population \n## nb: this needs to be collapsed for the overall population (i.e. removing health districts)\nsource_population <- population %>%\n ## ensure that age and sex are factors\n mutate(age_group = factor(age_group, \n levels = c(\"0-2\", \n \"3-14\", \n \"15-29\",\n \"30-44\", \n \"45+\")), \n sex = factor(sex)) %>% \n group_by(age_group, sex) %>% \n ## add the counts for each health district together \n summarise(population = sum(population)) %>% \n ## remove the grouping so can calculate overall proportion\n ungroup() %>% \n mutate(proportion = population / sum(population)) %>% \n ## plot pyramid \n age_pyramid(\n age_group = age_group, \n split_by = sex, \n count = proportion, \n proportional = TRUE) +\n ## only show the y axis label (otherwise repeated in all three plots)\n labs(title = \"Source population\", \n y = \"\", \n x = \"Age group (years)\") + \n ## make the x axis the same for all plots \n scale_y_continuous(breaks = breaks, \n limits = limits, \n labels = labels)\n \n \n## plot the unweighted sample population \nsample_population <- age_pyramid(survey_data, \n age_group = \"age_group\", \n split_by = \"sex\",\n proportion = TRUE) + \n ## only show the x axis label (otherwise repeated in all three plots)\n labs(title = \"Unweighted sample population\", \n y = \"Proportion (%)\", \n x = \"\") + \n ## make the x axis the same for all plots \n scale_y_continuous(breaks = breaks, \n limits = limits, \n labels = labels)\n\n\n## plot the weighted sample population \nweighted_population <- survey_design %>% \n ## make sure the variables are factors\n mutate(age_group = factor(age_group), \n sex = factor(sex)) %>%\n age_pyramid(\n age_group = \"age_group\",\n split_by = \"sex\", \n proportion = TRUE) +\n ## only show the x axis label (otherwise repeated in all three plots)\n labs(title = \"Weighted sample population\", \n y = \"\", \n x = \"\") + \n ## make the x axis the same for all plots \n scale_y_continuous(breaks = breaks, \n limits = limits, \n labels = labels)\n\n## combine all three plots ----------------------------------------------------\n## combine three plots next to eachother using + \nsource_population + sample_population + weighted_population + \n ## only show one legend and define theme \n ## note the use of & for combining theme with plot_layout()\n plot_layout(guides = \"collect\") & \n theme(legend.position = \"bottom\", # move legend to bottom\n legend.title = element_blank(), # remove title\n text = element_text(size = 18), # change text size\n axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1) # turn x-axis text\n )\n\n\n\n\n\n\n\n\n\n\n26.7.3 Alluvial/sankey diagram\nVisualising starting points and outcomes for individuals can be very helpful to get an overview. There is quite an obvious application for mobile populations, however there are numerous other applications such as cohorts or any other situation where there are transitions in states for individuals. These diagrams have several different names including alluvial, sankey and parallel sets - the details are in the handbook chapter on diagrams and charts.\n\n## summarize data\nflow_table <- survey_data %>%\n count(startcause, endcause, sex) %>% # get counts \n gather_set_data(x = c(\"startcause\", \"endcause\")) # change format for plotting\n\n\n## plot your dataset \n ## on the x axis is the start and end causes\n ## gather_set_data generates an ID for each possible combination\n ## splitting by y gives the possible start/end combos\n ## value as n gives it as counts (could also be changed to proportion)\nggplot(flow_table, aes(x, id = id, split = y, value = n)) +\n ## colour lines by sex \n geom_parallel_sets(aes(fill = sex), alpha = 0.5, axis.width = 0.2) +\n ## fill in the label boxes grey\n geom_parallel_sets_axes(axis.width = 0.15, fill = \"grey80\", color = \"grey80\") +\n ## change text colour and angle (needs to be adjusted)\n geom_parallel_sets_labels(color = \"black\", angle = 0, size = 5) +\n ## remove axis labels\n theme_void()+\n ## move legend to bottom\n theme(legend.position = \"bottom\")", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2309,7 +2309,7 @@ "href": "new_pages/survey_analysis.html#weighted-proportions", "title": "26  Survey analysis", "section": "26.8 Weighted proportions", - "text": "26.8 Weighted proportions\nThis section will detail how to produce tables for weighted counts and proportions, with associated confidence intervals and design effect. There are four different options using functions from the following packages: survey, srvyr, sitrep and gtsummary. For minimal coding to produce a standard epidemiology style table, we would recommend the sitrep function - which is a wrapper for srvyr code; note however that this is not yet on CRAN and may change in the future. Otherwise, the survey code is likely to be the most stable long-term, whereas srvyr will fit most nicely within tidyverse work-flows. While gtsummary functions hold a lot of potential, they appear to be experimental and incomplete at the time of writing.\n\n26.8.1 Survey package\nWe can use the svyciprop() function from survey to get weighted proportions and accompanying 95% confidence intervals. An appropriate design effect can be extracted using the svymean() rather than svyprop() function. It is worth noting that svyprop() only appears to accept variables between 0 and 1 (or TRUE/FALSE), so categorical variables will not work.\nNOTE: Functions from survey also accept srvyr design objects, but here we have used the survey design object just for consistency \n\n## produce weighted counts \nsvytable(~died, base_survey_design)\n\ndied\n FALSE TRUE \n1406244.43 76213.01 \n\n## produce weighted proportions\nsvyciprop(~died, base_survey_design, na.rm = T)\n\n 2.5% 97.5%\ndied 0.0514 0.0208 0.12\n\n## get the design effect \nsvymean(~died, base_survey_design, na.rm = T, deff = T) %>% \n deff()\n\ndiedFALSE diedTRUE \n 3.755508 3.755508 \n\n\nWe can combine the functions from survey shown above in to a function which we define ourselves below, called svy_prop; and we can then use that function together with map() from the purrr package to iterate over several variables and create a table. See the handbook iteration chapter for details on purrr.\n\n# Define function to calculate weighted counts, proportions, CI and design effect\n# x is the variable in quotation marks \n# design is your survey design object\n\nsvy_prop <- function(design, x) {\n \n ## put the variable of interest in a formula \n form <- as.formula(paste0( \"~\" , x))\n ## only keep the TRUE column of counts from svytable\n weighted_counts <- svytable(form, design)[[2]]\n ## calculate proportions (multiply by 100 to get percentages)\n weighted_props <- svyciprop(form, design, na.rm = TRUE) * 100\n ## extract the confidence intervals and multiply to get percentages\n weighted_confint <- confint(weighted_props) * 100\n ## use svymean to calculate design effect and only keep the TRUE column\n design_eff <- deff(svymean(form, design, na.rm = TRUE, deff = TRUE))[[TRUE]]\n \n ## combine in to one data frame\n full_table <- cbind(\n \"Variable\" = x,\n \"Count\" = weighted_counts,\n \"Proportion\" = weighted_props,\n weighted_confint, \n \"Design effect\" = design_eff\n )\n \n ## return table as a dataframe\n full_table <- data.frame(full_table, \n ## remove the variable names from rows (is a separate column now)\n row.names = NULL)\n \n ## change numerics back to numeric\n full_table[ , 2:6] <- as.numeric(full_table[, 2:6])\n \n ## return dataframe\n full_table\n}\n\n## iterate over several variables to create a table \npurrr::map(\n ## define variables of interest\n c(\"left\", \"died\", \"arrived\"), \n ## state function using and arguments for that function (design)\n svy_prop, design = base_survey_design) %>% \n ## collapse list in to a single data frame\n bind_rows() %>% \n ## round \n mutate(across(where(is.numeric), round, digits = 1))\n\n Variable Count Proportion X2.5. X97.5. Design.effect\n1 left 701199.1 47.3 39.2 55.5 2.4\n2 died 76213.0 5.1 2.1 12.1 3.8\n3 arrived 761799.0 51.4 40.9 61.7 3.9\n\n\n\n\n26.8.2 Srvyr package\nWith srvyr we can use dplyr syntax to create a table. Note that the survey_mean() function is used and the proportion argument is specified, and also that the same function is used to calculate design effect. This is because srvyr wraps around both of the survey package functions svyciprop() and svymean(), which are used in the above section.\nNOTE: It does not seem to be possible to get proportions from categorical variables using srvyr either, if you need this then check out the section below using sitrep \n\n## use the srvyr design object\nsurvey_design %>% \n summarise(\n ## produce the weighted counts \n counts = survey_total(died), \n ## produce weighted proportions and confidence intervals \n ## multiply by 100 to get a percentage \n props = survey_mean(died, \n proportion = TRUE, \n vartype = \"ci\") * 100, \n ## produce the design effect \n deff = survey_mean(died, deff = TRUE)) %>% \n ## only keep the rows of interest\n ## (drop standard errors and repeat proportion calculation)\n select(counts, props, props_low, props_upp, deff_deff)\n\n# A tibble: 1 × 5\n counts props props_low props_upp deff_deff\n <dbl> <dbl> <dbl> <dbl> <dbl>\n1 76213. 5.14 2.08 12.1 3.76\n\n\nHere too we could write a function to then iterate over multiple variables using the purrr package. See the handbook iteration chapter for details on purrr.\n\n# Define function to calculate weighted counts, proportions, CI and design effect\n# design is your survey design object\n# x is the variable in quotation marks \n\n\nsrvyr_prop <- function(design, x) {\n \n summarise(\n ## using the survey design object\n design, \n ## produce the weighted counts \n counts = survey_total(.data[[x]]), \n ## produce weighted proportions and confidence intervals \n ## multiply by 100 to get a percentage \n props = survey_mean(.data[[x]], \n proportion = TRUE, \n vartype = \"ci\") * 100, \n ## produce the design effect \n deff = survey_mean(.data[[x]], deff = TRUE)) %>% \n ## add in the variable name\n mutate(variable = x) %>% \n ## only keep the rows of interest\n ## (drop standard errors and repeat proportion calculation)\n select(variable, counts, props, props_low, props_upp, deff_deff)\n \n}\n \n\n## iterate over several variables to create a table \npurrr::map(\n ## define variables of interest\n c(\"left\", \"died\", \"arrived\"), \n ## state function using and arguments for that function (design)\n ~srvyr_prop(.x, design = survey_design)) %>% \n ## collapse list in to a single data frame\n bind_rows()\n\n# A tibble: 3 × 6\n variable counts props props_low props_upp deff_deff\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 left 701199. 47.3 39.2 55.5 2.38\n2 died 76213. 5.14 2.08 12.1 3.76\n3 arrived 761799. 51.4 40.9 61.7 3.93\n\n\n\n\n26.8.3 Sitrep package\nThe tab_survey() function from sitrep is a wrapper for srvyr, allowing you to create weighted tables with minimal coding. It also allows you to calculate weighted proportions for categorical variables.\n\n## using the survey design object\nsurvey_design %>% \n ## pass the names of variables of interest unquoted\n tab_survey(arrived, left, died, education_level,\n deff = TRUE, # calculate the design effect\n pretty = TRUE # merge the proportion and 95%CI\n )\n\nWarning: removing 257 missing value(s) from `education_level`\n\n\n# A tibble: 9 × 5\n variable value n deff ci \n <chr> <chr> <dbl> <dbl> <chr> \n1 arrived TRUE 761799. 3.93 51.4% (40.9-61.7)\n2 arrived FALSE 720658. 3.93 48.6% (38.3-59.1)\n3 left TRUE 701199. 2.38 47.3% (39.2-55.5)\n4 left FALSE 781258. 2.38 52.7% (44.5-60.8)\n5 died TRUE 76213. 3.76 5.1% (2.1-12.1) \n6 died FALSE 1406244. 3.76 94.9% (87.9-97.9)\n7 education_level higher 171644. 4.70 42.4% (26.9-59.7)\n8 education_level primary 102609. 2.37 25.4% (16.2-37.3)\n9 education_level secondary 130201. 6.68 32.2% (16.5-53.3)\n\n\n\n\n26.8.4 Gtsummary package\nWith gtsummary there does not seem to be inbuilt functions yet to add confidence intervals or design effect. Here we show how to define a function for adding confidence intervals and then add confidence intervals to a gtsummary table created using the tbl_svysummary() function.\n\nconfidence_intervals <- function(data, variable, by, ...) {\n \n ## extract the confidence intervals and multiply to get percentages\n props <- svyciprop(as.formula(paste0( \"~\" , variable)),\n data, na.rm = TRUE)\n \n ## extract the confidence intervals \n as.numeric(confint(props) * 100) %>% ## make numeric and multiply for percentage\n round(., digits = 1) %>% ## round to one digit\n c(.) %>% ## extract the numbers from matrix\n paste0(., collapse = \"-\") ## combine to single character\n}\n\n## using the survey package design object\ntbl_svysummary(base_survey_design, \n include = c(arrived, left, died), ## define variables want to include\n statistic = list(everything() ~ c(\"{n} ({p}%)\"))) %>% ## define stats of interest\n add_n() %>% ## add the weighted total \n add_stat(fns = everything() ~ confidence_intervals) %>% ## add CIs\n ## modify the column headers\n modify_header(\n list(\n n ~ \"**Weighted total (N)**\",\n stat_0 ~ \"**Weighted Count**\",\n add_stat_1 ~ \"**95%CI**\"\n )\n )\n\n\n\n\n\n\n\n\nCharacteristic\nWeighted total (N)\nWeighted Count1\n95%CI\n\n\n\n\narrived\n1,482,457\n761,799 (51%)\n40.9-61.7\n\n\nleft\n1,482,457\n701,199 (47%)\n39.2-55.5\n\n\ndied\n1,482,457\n76,213 (5.1%)\n2.1-12.1\n\n\n\n1 n (%)", + "text": "26.8 Weighted proportions\nThis section will detail how to produce tables for weighted counts and proportions, with associated confidence intervals and design effect. There are four different options using functions from the following packages: survey, srvyr, sitrep and gtsummary. For minimal coding to produce a standard epidemiology style table, we would recommend the sitrep function - which is a wrapper for srvyr code. Otherwise, the survey code is likely to be the most stable long-term, whereas srvyr will fit most nicely within tidyverse work-flows. As another alternative, gtsummary offers an easy way to display survey data following its update to versions >=2.0.\n\n26.8.1 Survey package\nWe can use the svyciprop() function from survey to get weighted proportions and accompanying 95% confidence intervals. An appropriate design effect can be extracted using the svymean() rather than svyprop() function. It is worth noting that svyprop() only appears to accept variables between 0 and 1 (or TRUE/FALSE), so categorical variables will not work.\nNOTE: Functions from survey also accept srvyr design objects, but here we have used the survey design object just for consistency \n\n## produce weighted counts \nsvytable(~died, base_survey_design)\n\ndied\n FALSE TRUE \n1406244.43 76213.01 \n\n## produce weighted proportions\nsvyciprop(~died, base_survey_design, na.rm = T)\n\n 2.5% 97.5%\ndied 0.0514 0.0208 0.1213\n\n## get the design effect \nsvymean(~died, base_survey_design, na.rm = T, deff = T) %>% \n deff()\n\ndiedFALSE diedTRUE \n 3.755508 3.755508 \n\n\nWe can combine the functions from survey shown above in to a function which we define ourselves below, called svy_prop; and we can then use that function together with map() from the purrr package to iterate over several variables and create a table. See the handbook iteration chapter for details on purrr.\n\n# Define function to calculate weighted counts, proportions, CI and design effect\n# x is the variable in quotation marks \n# design is your survey design object\n\nsvy_prop <- function(design, x) {\n \n ## put the variable of interest in a formula \n form <- as.formula(paste0( \"~\" , x))\n ## only keep the TRUE column of counts from svytable\n weighted_counts <- svytable(form, design)[[2]]\n ## calculate proportions (multiply by 100 to get percentages)\n weighted_props <- svyciprop(form, design, na.rm = TRUE) * 100\n ## extract the confidence intervals and multiply to get percentages\n weighted_confint <- confint(weighted_props) * 100\n ## use svymean to calculate design effect and only keep the TRUE column\n design_eff <- deff(svymean(form, design, na.rm = TRUE, deff = TRUE))[[TRUE]]\n \n ## combine in to one data frame\n full_table <- cbind(\n \"Variable\" = x,\n \"Count\" = weighted_counts,\n \"Proportion\" = weighted_props,\n weighted_confint, \n \"Design effect\" = design_eff\n )\n \n ## return table as a dataframe\n full_table <- data.frame(full_table, \n ## remove the variable names from rows (is a separate column now)\n row.names = NULL)\n \n ## change numerics back to numeric\n full_table[ , 2:6] <- as.numeric(full_table[, 2:6])\n \n ## return dataframe\n full_table\n}\n\n## iterate over several variables to create a table \npurrr::map(\n ## define variables of interest\n c(\"left\", \"died\", \"arrived\"), \n ## state function using and arguments for that function (design)\n svy_prop, design = base_survey_design) %>% \n ## collapse list in to a single data frame\n bind_rows() %>% \n ## round \n mutate(across(where(is.numeric), round, digits = 1))\n\n Variable Count Proportion X2.5. X97.5. Design.effect\n1 left 701199.1 47.3 39.2 55.5 2.4\n2 died 76213.0 5.1 2.1 12.1 3.8\n3 arrived 761799.0 51.4 40.9 61.7 3.9\n\n\n\n\n26.8.2 Srvyr package\nWith srvyr we can use dplyr syntax to create a table. Note that the survey_mean() function is used and the proportion argument is specified, and also that the same function is used to calculate design effect. This is because srvyr wraps around both of the survey package functions svyciprop() and svymean(), which are used in the above section.\nNOTE: It does not seem to be possible to get proportions from categorical variables using srvyr either, if you need this then check out the section below using sitrep \n\n## use the srvyr design object\nsurvey_design %>% \n summarise(\n ## produce the weighted counts \n counts = survey_total(died), \n ## produce weighted proportions and confidence intervals \n ## multiply by 100 to get a percentage \n props = survey_mean(died, \n proportion = TRUE, \n vartype = \"ci\") * 100, \n ## produce the design effect \n deff = survey_mean(died, deff = TRUE)) %>% \n ## only keep the rows of interest\n ## (drop standard errors and repeat proportion calculation)\n select(counts, props, props_low, props_upp, deff_deff)\n\n# A tibble: 1 × 5\n counts props props_low props_upp deff_deff\n <dbl> <dbl> <dbl> <dbl> <dbl>\n1 76213. 5.14 2.08 12.1 3.76\n\n\nHere too we could write a function to then iterate over multiple variables using the purrr package. See the handbook iteration chapter for details on purrr.\n\n# Define function to calculate weighted counts, proportions, CI and design effect\n# design is your survey design object\n# x is the variable in quotation marks \n\n\nsrvyr_prop <- function(design, x) {\n \n summarise(\n ## using the survey design object\n design, \n ## produce the weighted counts \n counts = survey_total(.data[[x]]), \n ## produce weighted proportions and confidence intervals \n ## multiply by 100 to get a percentage \n props = survey_mean(.data[[x]], \n proportion = TRUE, \n vartype = \"ci\") * 100, \n ## produce the design effect \n deff = survey_mean(.data[[x]], deff = TRUE)) %>% \n ## add in the variable name\n mutate(variable = x) %>% \n ## only keep the rows of interest\n ## (drop standard errors and repeat proportion calculation)\n select(variable, counts, props, props_low, props_upp, deff_deff)\n \n}\n \n\n## iterate over several variables to create a table \npurrr::map(\n ## define variables of interest\n c(\"left\", \"died\", \"arrived\"), \n ## state function using and arguments for that function (design)\n ~srvyr_prop(.x, design = survey_design)) %>% \n ## collapse list in to a single data frame\n bind_rows()\n\n# A tibble: 3 × 6\n variable counts props props_low props_upp deff_deff\n <chr> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 left 701199. 47.3 39.2 55.5 2.38\n2 died 76213. 5.14 2.08 12.1 3.76\n3 arrived 761799. 51.4 40.9 61.7 3.93\n\n\n\n\n26.8.3 Sitrep package\nThe tab_survey() function from sitrep is a wrapper for srvyr, allowing you to create weighted tables with minimal coding. It also allows you to calculate weighted proportions for categorical variables.\n\n## using the survey design object\nsurvey_design %>% \n ## pass the names of variables of interest unquoted\n tab_survey(\n \"arrived\", \n \"left\", \n \"died\", \n \"education_level\",\n deff = TRUE, # calculate the design effect\n pretty = TRUE # merge the proportion and 95%CI\n )\n\n\n\n variable value n deff ci\n1 arrived TRUE 761799.05 3.925504 51.4% (40.9-61.7)\n2 arrived FALSE 720658.39 3.925504 48.6% (38.3-59.1)\n3 left TRUE 701199.14 2.379761 47.3% (39.2-55.5)\n4 left FALSE 781258.30 2.379761 52.7% (44.5-60.8)\n5 died TRUE 76213.01 3.755508 5.1% (2.1-12.1)\n6 died FALSE 1406244.43 3.755508 94.9% (87.9-97.9)\n7 education_level higher 171644.18 4.701398 42.4% (26.9-59.7)\n8 education_level primary 102609.44 2.368384 25.4% (16.2-37.3)\n9 education_level secondary 130200.67 6.677178 32.2% (16.5-53.3)\n\n\n\n\n26.8.4 Gtsummary package\nWith gtsummary we can use the function tbl_svysummary() and the addition add_ci() to add confidence intervals.\n\n## using the survey package design object\ntbl_svysummary(data = base_survey_design, \n include = c(arrived, left, died), ## define variables want to include\n statistic = list(everything() ~ \"{n} ({p}%)\")) %>% ## define stats of interest\n add_ci() %>% ## add confidence intervals\n add_n() %>%\n modify_header(label = list(\n n ~ \"**Weighted total (N)**\",\n stat_0 ~ \"**Weighted count**\"\n ))\n\nWarning: The `update` argument of `modify_header()` is deprecated as of gtsummary 2.0.0.\nℹ Use `modify_header(...)` input instead. Dynamic dots allow for syntax like\n `modify_header(!!!list(...))`.\nℹ The deprecated feature was likely used in the gtsummary package.\n Please report the issue at <https://github.com/ddsjoberg/gtsummary/issues>.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nWeighted total (N)\nWeighted count\n1\n95% CI\n2\n\n\n\n\narrived\n1,482,457\n761,799 (51%)\n41%, 62%\n\n\nleft\n1,482,457\n701,199 (47%)\n39%, 56%\n\n\ndied\n1,482,457\n76,213 (5.1%)\n2.1%, 12%\n\n\n\n1\nn (%)\n\n\n2\nCI = Confidence Interval", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2330,8 +2330,8 @@ "objectID": "new_pages/survey_analysis.html#resources", "href": "new_pages/survey_analysis.html#resources", "title": "26  Survey analysis", - "section": "26.10 Resources", - "text": "26.10 Resources\nUCLA stats page\nAnalyze survey data free\nsrvyr packge\ngtsummary package\nEPIET survey case studies", + "section": "26.11 Resources", + "text": "26.11 Resources\nUCLA stats page\nAnalyze survey data free\nsrvyr packge\ngtsummary package\nEPIET survey case studies\nAnalyzing Complex Survey Data", "crumbs": [ "Analysis", "26  Survey analysis" @@ -2342,7 +2342,7 @@ "href": "new_pages/survival_analysis.html", "title": "27  Survival analysis", "section": "", - "text": "27.1 Overview\nSurvival analysis focuses on describing for a given individual or group of individuals, a defined point of event called the failure (occurrence of a disease, cure from a disease, death, relapse after response to treatment…) that occurs after a period of time called failure time (or follow-up time in cohort/population-based studies) during which individuals are observed. To determine the failure time, it is then necessary to define a time of origin (that can be the inclusion date, the date of diagnosis…).\nThe target of inference for survival analysis is then the time between an origin and an event. In current medical research, it is widely used in clinical studies to assess the effect of a treatment for instance, or in cancer epidemiology to assess a large variety of cancer survival measures.\nIt is usually expressed through the survival probability which is the probability that the event of interest has not occurred by a duration t.\nCensoring: Censoring occurs when at the end of follow-up, some of the individuals have not had the event of interest, and thus their true time to event is unknown. We will mostly focus on right censoring here but for more details on censoring and survival analysis in general, you can see references.", + "text": "27.1 Overview\nSurvival analysis focuses on describing for a given individual or group of individuals, a defined point of event called the failure (occurrence of a disease, cure from a disease, death, relapse after response to treatment…) that occurs after a period of time called failure time (or follow-up time in cohort/population-based studies) during which individuals are observed. To determine the failure time, it is then necessary to define a time of origin (that can be the inclusion date, the date of diagnosis, etc.).\nThe target of inference for survival analysis is then the time between an origin and an event. In current medical research, it is widely used in clinical studies to assess the effect of a treatment for instance, or in cancer epidemiology to assess a large variety of cancer survival measures.\nIt is usually expressed through the survival probability which is the probability that the event of interest has not occurred by a duration t.\nCensoring: Censoring occurs when at the end of follow-up, some of the individuals have not had the event of interest, and thus their true time to event is unknown. We will mostly focus on right censoring here but for more details on censoring and survival analysis in general, you can see references.", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2353,7 +2353,7 @@ "href": "new_pages/survival_analysis.html#preparation", "title": "27  Survival analysis", "section": "27.2 Preparation", - "text": "27.2 Preparation\n\nLoad packages\nTo run survival analyses in R, one the most widely used package is the survival package. We first install it and then load it as well as the other packages that will be used in this section:\nIn this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\nThis page explores survival analyses using the linelist used in most of the previous pages and on which we apply some changes to have a proper survival data.\n\n\nImport dataset\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import linelist\nlinelist_case_data <- rio::import(\"linelist_cleaned.rds\")\n\n\n\nData management and transformation\nIn short, survival data can be described as having the following three characteristics:\n\nthe dependent variable or response is the waiting time until the occurrence of a well-defined event,\nobservations are censored, in the sense that for some units the event of interest has not occurred at the time the data are analyzed, and\nthere are predictors or explanatory variables whose effect on the waiting time we wish to assess or control.\n\nThus, we will create different variables needed to respect that structure and run the survival analysis.\nWe define:\n\na new data frame linelist_surv for this analysis\n\nour event of interest as being “death” (hence our survival probability will be the probability of being alive after a certain time after the time of origin),\nthe follow-up time (futime) as the time between the time of onset and the time of outcome in days,\ncensored patients as those who recovered or for whom the final outcome is not known ie the event “death” was not observed (event=0).\n\nCAUTION: Since in a real cohort study, the information on the time of origin and the end of the follow-up is known given individuals are observed, we will remove observations where the date of onset or the date of outcome is unknown. Also the cases where the date of onset is later than the date of outcome will be removed since they are considered as wrong.\nTIP: Given that filtering to greater than (>) or less than (<) a date can remove rows with missing values, applying the filter on the wrong dates will also remove the rows with missing dates.\nWe then use case_when() to create a column age_cat_small in which there are only 3 age categories.\n\n#create a new data called linelist_surv from the linelist_case_data\n\nlinelist_surv <- linelist_case_data %>% \n \n dplyr::filter(\n # remove observations with wrong or missing dates of onset or date of outcome\n date_outcome > date_onset) %>% \n \n dplyr::mutate(\n # create the event var which is 1 if the patient died and 0 if he was right censored\n event = ifelse(is.na(outcome) | outcome == \"Recover\", 0, 1), \n \n # create the var on the follow-up time in days\n futime = as.double(date_outcome - date_onset), \n \n # create a new age category variable with only 3 strata levels\n age_cat_small = dplyr::case_when( \n age_years < 5 ~ \"0-4\",\n age_years >= 5 & age_years < 20 ~ \"5-19\",\n age_years >= 20 ~ \"20+\"),\n \n # previous step created age_cat_small var as character.\n # now convert it to factor and specify the levels.\n # Note that the NA values remain NA's and are not put in a level \"unknown\" for example,\n # since in the next analyses they have to be removed.\n age_cat_small = fct_relevel(age_cat_small, \"0-4\", \"5-19\", \"20+\")\n )\n\nTIP: We can verify the new columns we have created by doing a summary on the futime and a cross-tabulation between event and outcome from which it was created. Besides this verification it is a good habit to communicate the median follow-up time when interpreting survival analysis results.\n\nsummary(linelist_surv$futime)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. \n 1.00 6.00 10.00 11.98 16.00 64.00 \n\n# cross tabulate the new event var and the outcome var from which it was created\n# to make sure the code did what it was intended to\nlinelist_surv %>% \n tabyl(outcome, event)\n\n outcome 0 1\n Death 0 1952\n Recover 1547 0\n <NA> 1040 0\n\n\nNow we cross-tabulate the new age_cat_small var and the old age_cat col to ensure correct assingments\n\nlinelist_surv %>% \n tabyl(age_cat_small, age_cat)\n\n age_cat_small 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+ NA_\n 0-4 834 0 0 0 0 0 0 0 0\n 5-19 0 852 717 575 0 0 0 0 0\n 20+ 0 0 0 0 862 554 69 5 0\n <NA> 0 0 0 0 0 0 0 0 71\n\n\nNow we review the 10 first observations of the linelist_surv data looking at specific variables (including those newly created).\n\nlinelist_surv %>% \n select(case_id, age_cat_small, date_onset, date_outcome, outcome, event, futime) %>% \n head(10)\n\n case_id age_cat_small date_onset date_outcome outcome event futime\n1 8689b7 0-4 2014-05-13 2014-05-18 Recover 0 5\n2 11f8ea 20+ 2014-05-16 2014-05-30 Recover 0 14\n3 893f25 0-4 2014-05-21 2014-05-29 Recover 0 8\n4 be99c8 5-19 2014-05-22 2014-05-24 Recover 0 2\n5 07e3e8 5-19 2014-05-27 2014-06-01 Recover 0 5\n6 369449 0-4 2014-06-02 2014-06-07 Death 1 5\n7 f393b4 20+ 2014-06-05 2014-06-18 Recover 0 13\n8 1389ca 20+ 2014-06-05 2014-06-09 Death 1 4\n9 2978ac 5-19 2014-06-06 2014-06-15 Death 1 9\n10 fc15ef 5-19 2014-06-16 2014-07-09 Recover 0 23\n\n\nWe can also cross-tabulate the columns age_cat_small and gender to have more details on the distribution of this new column by gender. We use tabyl() and the adorn functions from janitor as described in the Descriptive tables page.\n\n\nlinelist_surv %>% \n tabyl(gender, age_cat_small, show_na = F) %>% \n adorn_totals(where = \"both\") %>% \n adorn_percentages() %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\")\n\n gender 0-4 5-19 20+ Total\n f 482 (22.4%) 1,184 (54.9%) 490 (22.7%) 2,156 (100.0%)\n m 325 (15.0%) 880 (40.6%) 960 (44.3%) 2,165 (100.0%)\n Total 807 (18.7%) 2,064 (47.8%) 1,450 (33.6%) 4,321 (100.0%)", + "text": "27.2 Preparation\n\nLoad packages\nTo run survival analyses in R, one the most widely used package is the survival package. We first install it and then load it as well as the other packages that will be used in this section:\nIn this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\nThis page explores survival analyses using the linelist used in most of the previous pages and on which we apply some changes to have a proper survival data.\n\n\nImport dataset\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import linelist\nlinelist_case_data <- rio::import(\"linelist_cleaned.rds\")\n\n\n\nData management and transformation\nIn short, survival data can be described as having the following three characteristics:\n\nThe dependent variable or response is the waiting time until the occurrence of a well-defined event,\nobservations are censored, in the sense that for some units the event of interest has not occurred at the time the data are analyzed, and\nthere are predictors or explanatory variables whose effect on the waiting time we wish to assess or control.\n\nThus, we will create different variables needed to respect that structure and run the survival analysis.\nWe define:\n\nA new data frame linelist_surv for this analysis.\n\nThe event of interest as being “death” (hence our survival probability will be the probability of being alive after a certain time after the time of origin),\nthe follow-up time (futime) as the time between the time of onset and the time of outcome in days,\ncensored patients as those who recovered or for whom the final outcome is not known ie the event “death” was not observed (event=0).\n\nCAUTION: Since in a real cohort study, the information on the time of origin and the end of the follow-up is known given individuals are observed, we will remove observations where the date of onset or the date of outcome is unknown. Also the cases where the date of onset is later than the date of outcome will be removed since they are considered as wrong.\nTIP: Given that filtering to greater than (>) or less than (<) a date can remove rows with missing values, applying the filter on the wrong dates will also remove the rows with missing dates.\nWe then use case_when() to create a column age_cat_small in which there are only 3 age categories.\n\n#create a new data called linelist_surv from the linelist_case_data\n\nlinelist_surv <- linelist_case_data %>% \n \n dplyr::filter(\n # remove observations with wrong or missing dates of onset or date of outcome\n date_outcome > date_onset) %>% \n \n dplyr::mutate(\n # create the event var which is 1 if the patient died and 0 if he was right censored\n event = ifelse(is.na(outcome) | outcome == \"Recover\", 0, 1), \n \n # create the var on the follow-up time in days\n futime = as.double(date_outcome - date_onset), \n \n # create a new age category variable with only 3 strata levels\n age_cat_small = dplyr::case_when( \n age_years < 5 ~ \"0-4\",\n age_years >= 5 & age_years < 20 ~ \"5-19\",\n age_years >= 20 ~ \"20+\"),\n \n # previous step created age_cat_small var as character.\n # now convert it to factor and specify the levels.\n # Note that the NA values remain NA's and are not put in a level \"unknown\" for example,\n # since in the next analyses they have to be removed.\n age_cat_small = fct_relevel(age_cat_small, \"0-4\", \"5-19\", \"20+\")\n )\n\nTIP: We can verify the new columns we have created by doing a summary on the futime and a cross-tabulation between event and outcome from which it was created. Besides this verification it is a good habit to communicate the median follow-up time when interpreting survival analysis results.\n\nsummary(linelist_surv$futime)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. \n 1.00 6.00 10.00 11.98 16.00 64.00 \n\n# cross tabulate the new event var and the outcome var from which it was created\n# to make sure the code did what it was intended to\nlinelist_surv %>% \n tabyl(outcome, event)\n\n outcome 0 1\n Death 0 1952\n Recover 1547 0\n <NA> 1040 0\n\n\nNow we cross-tabulate the new age_cat_small var and the old age_cat col to ensure correct assignments.\n\nlinelist_surv %>% \n tabyl(age_cat_small, age_cat)\n\n age_cat_small 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+ NA_\n 0-4 834 0 0 0 0 0 0 0 0\n 5-19 0 852 717 575 0 0 0 0 0\n 20+ 0 0 0 0 862 554 69 5 0\n <NA> 0 0 0 0 0 0 0 0 71\n\n\nNow we review the 10 first observations of the linelist_surv data looking at specific variables (including those newly created).\n\nlinelist_surv %>% \n select(case_id, age_cat_small, date_onset, date_outcome, outcome, event, futime) %>% \n head(10)\n\n case_id age_cat_small date_onset date_outcome outcome event futime\n1 8689b7 0-4 2014-05-13 2014-05-18 Recover 0 5\n2 11f8ea 20+ 2014-05-16 2014-05-30 Recover 0 14\n3 893f25 0-4 2014-05-21 2014-05-29 Recover 0 8\n4 be99c8 5-19 2014-05-22 2014-05-24 Recover 0 2\n5 07e3e8 5-19 2014-05-27 2014-06-01 Recover 0 5\n6 369449 0-4 2014-06-02 2014-06-07 Death 1 5\n7 f393b4 20+ 2014-06-05 2014-06-18 Recover 0 13\n8 1389ca 20+ 2014-06-05 2014-06-09 Death 1 4\n9 2978ac 5-19 2014-06-06 2014-06-15 Death 1 9\n10 fc15ef 5-19 2014-06-16 2014-07-09 Recover 0 23\n\n\nWe can also cross-tabulate the columns age_cat_small and gender to have more details on the distribution of this new column by gender. We use tabyl() and the adorn functions from janitor as described in the Descriptive tables page.\n\n\nlinelist_surv %>% \n tabyl(gender, age_cat_small, show_na = F) %>% \n adorn_totals(where = \"both\") %>% \n adorn_percentages() %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\")\n\n gender 0-4 5-19 20+ Total\n f 482 (22.4%) 1,184 (54.9%) 490 (22.7%) 2,156 (100.0%)\n m 325 (15.0%) 880 (40.6%) 960 (44.3%) 2,165 (100.0%)\n Total 807 (18.7%) 2,064 (47.8%) 1,450 (33.6%) 4,321 (100.0%)", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2364,7 +2364,7 @@ "href": "new_pages/survival_analysis.html#basics-of-survival-analysis", "title": "27  Survival analysis", "section": "27.3 Basics of survival analysis", - "text": "27.3 Basics of survival analysis\n\nBuilding a surv-type object\nWe will first use Surv() from survival to build a survival object from the follow-up time and event columns.\nThe result of such a step is to produce an object of type Surv that condenses the time information and whether the event of interest (death) was observed. This object will ultimately be used in the right-hand side of subsequent model formulae (see documentation).\n\n# Use Suv() syntax for right-censored data\nsurvobj <- Surv(time = linelist_surv$futime,\n event = linelist_surv$event)\n\n\n\n\n\n\nTo review, here are the first 10 rows of the linelist_surv data, viewing only some important columns.\n\nlinelist_surv %>% \n select(case_id, date_onset, date_outcome, futime, outcome, event) %>% \n head(10)\n\n case_id date_onset date_outcome futime outcome event\n1 8689b7 2014-05-13 2014-05-18 5 Recover 0\n2 11f8ea 2014-05-16 2014-05-30 14 Recover 0\n3 893f25 2014-05-21 2014-05-29 8 Recover 0\n4 be99c8 2014-05-22 2014-05-24 2 Recover 0\n5 07e3e8 2014-05-27 2014-06-01 5 Recover 0\n6 369449 2014-06-02 2014-06-07 5 Death 1\n7 f393b4 2014-06-05 2014-06-18 13 Recover 0\n8 1389ca 2014-06-05 2014-06-09 4 Death 1\n9 2978ac 2014-06-06 2014-06-15 9 Death 1\n10 fc15ef 2014-06-16 2014-07-09 23 Recover 0\n\n\nAnd here are the first 10 elements of survobj. It prints as essentially a vector of follow-up time, with “+” to represent if an observation was right-censored. See how the numbers align above and below.\n\n#print the 50 first elements of the vector to see how it presents\nhead(survobj, 10)\n\n [1] 5+ 14+ 8+ 2+ 5+ 5 13+ 4 9 23+\n\n\n\n\nRunning initial analyses\nWe then start our analysis using the survfit() function to produce a survfit object, which fits the default calculations for Kaplan Meier (KM) estimates of the overall (marginal) survival curve, which are in fact a step function with jumps at observed event times. The final survfit object contains one or more survival curves and is created using the Surv object as a response variable in the model formula.\nNOTE: The Kaplan-Meier estimate is a nonparametric maximum likelihood estimate (MLE) of the survival function. . (see resources for more information).\nThe summary of this survfit object will give what is called a life table. For each time step of the follow-up (time) where an event happened (in ascending order):\n\nthe number of people who were at risk of developing the event (people who did not have the event yet nor were censored: n.risk)\n\nthose who did develop the event (n.event)\n\nand from the above: the probability of not developing the event (probability of not dying, or of surviving past that specific time)\n\nfinally, the standard error and the confidence interval for that probability are derived and displayed\n\nWe fit the KM estimates using the formula where the previously Surv object “survobj” is the response variable. “~ 1” precises we run the model for the overall survival.\n\n# fit the KM estimates using a formula where the Surv object \"survobj\" is the response variable.\n# \"~ 1\" signifies that we run the model for the overall survival \nlinelistsurv_fit <- survival::survfit(survobj ~ 1)\n\n#print its summary for more details\nsummary(linelistsurv_fit)\n\nCall: survfit(formula = survobj ~ 1)\n\n time n.risk n.event survival std.err lower 95% CI upper 95% CI\n 1 4539 30 0.993 0.00120 0.991 0.996\n 2 4500 69 0.978 0.00217 0.974 0.982\n 3 4394 149 0.945 0.00340 0.938 0.952\n 4 4176 194 0.901 0.00447 0.892 0.910\n 5 3899 214 0.852 0.00535 0.841 0.862\n 6 3592 210 0.802 0.00604 0.790 0.814\n 7 3223 179 0.757 0.00656 0.745 0.770\n 8 2899 167 0.714 0.00700 0.700 0.728\n 9 2593 145 0.674 0.00735 0.660 0.688\n 10 2311 109 0.642 0.00761 0.627 0.657\n 11 2081 119 0.605 0.00788 0.590 0.621\n 12 1843 89 0.576 0.00809 0.560 0.592\n 13 1608 55 0.556 0.00823 0.540 0.573\n 14 1448 43 0.540 0.00837 0.524 0.556\n 15 1296 31 0.527 0.00848 0.511 0.544\n 16 1152 48 0.505 0.00870 0.488 0.522\n 17 1002 29 0.490 0.00886 0.473 0.508\n 18 898 21 0.479 0.00900 0.462 0.497\n 19 798 7 0.475 0.00906 0.457 0.493\n 20 705 4 0.472 0.00911 0.454 0.490\n 21 626 13 0.462 0.00932 0.444 0.481\n 22 546 8 0.455 0.00948 0.437 0.474\n 23 481 5 0.451 0.00962 0.432 0.470\n 24 436 4 0.447 0.00975 0.428 0.466\n 25 378 4 0.442 0.00993 0.423 0.462\n 26 336 3 0.438 0.01010 0.419 0.458\n 27 297 1 0.436 0.01017 0.417 0.457\n 29 235 1 0.435 0.01030 0.415 0.455\n 38 73 1 0.429 0.01175 0.406 0.452\n\n\nWhile using summary() we can add the option times and specify certain times at which we want to see the survival information\n\n#print its summary at specific times\nsummary(linelistsurv_fit, times = c(5,10,20,30,60))\n\nCall: survfit(formula = survobj ~ 1)\n\n time n.risk n.event survival std.err lower 95% CI upper 95% CI\n 5 3899 656 0.852 0.00535 0.841 0.862\n 10 2311 810 0.642 0.00761 0.627 0.657\n 20 705 446 0.472 0.00911 0.454 0.490\n 30 210 39 0.435 0.01030 0.415 0.455\n 60 2 1 0.429 0.01175 0.406 0.452\n\n\nWe can also use the print() function. The print.rmean = TRUE argument is used to obtain the mean survival time and its standard error (se).\nNOTE: The restricted mean survival time (RMST) is a specific survival measure more and more used in cancer survival analysis and which is often defined as the area under the survival curve, given we observe patients up to restricted time T (more details in Resources section).\n\n# print linelistsurv_fit object with mean survival time and its se. \nprint(linelistsurv_fit, print.rmean = TRUE)\n\nCall: survfit(formula = survobj ~ 1)\n\n n events rmean* se(rmean) median 0.95LCL 0.95UCL\n[1,] 4539 1952 33.1 0.539 17 16 18\n * restricted mean with upper limit = 64 \n\n\nTIP: We can create the surv object directly in the survfit() function and save a line of code. This will then look like: linelistsurv_quick <- survfit(Surv(futime, event) ~ 1, data=linelist_surv).\n\n\nCumulative hazard\nBesides the summary() function, we can also use the str() function that gives more details on the structure of the survfit() object. It is a list of 16 elements.\nAmong these elements is an important one: cumhaz, which is a numeric vector. This could be plotted to allow show the cumulative hazard, with the hazard being the instantaneous rate of event occurrence (see references).\n\nstr(linelistsurv_fit)\n\nList of 16\n $ n : int 4539\n $ time : num [1:59] 1 2 3 4 5 6 7 8 9 10 ...\n $ n.risk : num [1:59] 4539 4500 4394 4176 3899 ...\n $ n.event : num [1:59] 30 69 149 194 214 210 179 167 145 109 ...\n $ n.censor : num [1:59] 9 37 69 83 93 159 145 139 137 121 ...\n $ surv : num [1:59] 0.993 0.978 0.945 0.901 0.852 ...\n $ std.err : num [1:59] 0.00121 0.00222 0.00359 0.00496 0.00628 ...\n $ cumhaz : num [1:59] 0.00661 0.02194 0.05585 0.10231 0.15719 ...\n $ std.chaz : num [1:59] 0.00121 0.00221 0.00355 0.00487 0.00615 ...\n $ type : chr \"right\"\n $ logse : logi TRUE\n $ conf.int : num 0.95\n $ conf.type: chr \"log\"\n $ lower : num [1:59] 0.991 0.974 0.938 0.892 0.841 ...\n $ upper : num [1:59] 0.996 0.982 0.952 0.91 0.862 ...\n $ call : language survfit(formula = survobj ~ 1)\n - attr(*, \"class\")= chr \"survfit\"\n\n\n\n\n\nPlotting Kaplan-Meir curves\nOnce the KM estimates are fitted, we can visualize the probability of being alive through a given time using the basic plot() function that draws the “Kaplan-Meier curve”. In other words, the curve below is a conventional illustration of the survival experience in the whole patient group.\nWe can quickly verify the follow-up time min and max on the curve.\nAn easy way to interpret is to say that at time zero, all the participants are still alive and survival probability is then 100%. This probability decreases over time as patients die. The proportion of participants surviving past 60 days of follow-up is around 40%.\n\nplot(linelistsurv_fit, \n xlab = \"Days of follow-up\", # x-axis label\n ylab=\"Survival Probability\", # y-axis label\n main= \"Overall survival curve\" # figure title\n )\n\n\n\n\n\n\n\n\nThe confidence interval of the KM survival estimates are also plotted by default and can be dismissed by adding the option conf.int = FALSE to the plot() command.\nSince the event of interest is “death”, drawing a curve describing the complements of the survival proportions will lead to drawing the cumulative mortality proportions. This can be done with lines(), which adds information to an existing plot.\n\n# original plot\nplot(\n linelistsurv_fit,\n xlab = \"Days of follow-up\", \n ylab = \"Survival Probability\", \n mark.time = TRUE, # mark events on the curve: a \"+\" is printed at every event\n conf.int = FALSE, # do not plot the confidence interval\n main = \"Overall survival curve and cumulative mortality\"\n )\n\n# draw an additional curve to the previous plot\nlines(\n linelistsurv_fit,\n lty = 3, # use different line type for clarity\n fun = \"event\", # draw the cumulative events instead of the survival \n mark.time = FALSE,\n conf.int = FALSE\n )\n\n# add a legend to the plot\nlegend(\n \"topright\", # position of legend\n legend = c(\"Survival\", \"Cum. Mortality\"), # legend text \n lty = c(1, 3), # line types to use in the legend\n cex = .85, # parametes that defines size of legend text\n bty = \"n\" # no box type to be drawn for the legend\n )", + "text": "27.3 Basics of survival analysis\n\nBuilding a surv-type object\nWe will first use Surv() from survival to build a survival object from the follow-up time and event columns.\nThe result of such a step is to produce an object of type Surv that condenses the time information and whether the event of interest (death) was observed. This object will ultimately be used in the right-hand side of subsequent model formulae (see documentation).\n\n# Use Suv() syntax for right-censored data\nsurvobj <- Surv(time = linelist_surv$futime,\n event = linelist_surv$event)\n\n\n\n\n\n\nTo review, here are the first 10 rows of the linelist_surv data, viewing only some important columns.\n\nlinelist_surv %>% \n select(case_id, date_onset, date_outcome, futime, outcome, event) %>% \n head(10)\n\n case_id date_onset date_outcome futime outcome event\n1 8689b7 2014-05-13 2014-05-18 5 Recover 0\n2 11f8ea 2014-05-16 2014-05-30 14 Recover 0\n3 893f25 2014-05-21 2014-05-29 8 Recover 0\n4 be99c8 2014-05-22 2014-05-24 2 Recover 0\n5 07e3e8 2014-05-27 2014-06-01 5 Recover 0\n6 369449 2014-06-02 2014-06-07 5 Death 1\n7 f393b4 2014-06-05 2014-06-18 13 Recover 0\n8 1389ca 2014-06-05 2014-06-09 4 Death 1\n9 2978ac 2014-06-06 2014-06-15 9 Death 1\n10 fc15ef 2014-06-16 2014-07-09 23 Recover 0\n\n\nAnd here are the first 10 elements of survobj. It prints as essentially a vector of follow-up time, with “+” to represent if an observation was right-censored. See how the numbers align above and below.\n\n#print the 50 first elements of the vector to see how it presents\nhead(survobj, 10)\n\n [1] 5+ 14+ 8+ 2+ 5+ 5 13+ 4 9 23+\n\n\n\n\nRunning initial analyses\nWe then start our analysis using the survfit() function to produce a survfit object, which fits the default calculations for Kaplan Meier (KM) estimates of the overall (marginal) survival curve, which are in fact a step function with jumps at observed event times. The final survfit object contains one or more survival curves and is created using the Surv object as a response variable in the model formula.\nNOTE: The Kaplan-Meier estimate is a nonparametric maximum likelihood estimate (MLE) of the survival function. . (see resources for more information).\nThe summary of this survfit object will give what is called a life table. For each time step of the follow-up (time) where an event happened (in ascending order):\n\nThe number of people who were at risk of developing the event (people who did not have the event yet nor were censored: n.risk).\nThose who did develop the event (n.event),\nand from the above: the probability of not developing the event (probability of not dying, or of surviving past that specific time).\n\nFinally, the standard error and the confidence interval for that probability are derived and displayed.\n\nWe fit the KM estimates using the formula where the previously Surv object “survobj” is the response variable. “~ 1” precises we run the model for the overall survival.\n\n# fit the KM estimates using a formula where the Surv object \"survobj\" is the response variable.\n# \"~ 1\" signifies that we run the model for the overall survival \nlinelistsurv_fit <- survival::survfit(survobj ~ 1)\n\n#print its summary for more details\nsummary(linelistsurv_fit)\n\nCall: survfit(formula = survobj ~ 1)\n\n time n.risk n.event survival std.err lower 95% CI upper 95% CI\n 1 4539 30 0.993 0.00120 0.991 0.996\n 2 4500 69 0.978 0.00217 0.974 0.982\n 3 4394 149 0.945 0.00340 0.938 0.952\n 4 4176 194 0.901 0.00447 0.892 0.910\n 5 3899 214 0.852 0.00535 0.841 0.862\n 6 3592 210 0.802 0.00604 0.790 0.814\n 7 3223 179 0.757 0.00656 0.745 0.770\n 8 2899 167 0.714 0.00700 0.700 0.728\n 9 2593 145 0.674 0.00735 0.660 0.688\n 10 2311 109 0.642 0.00761 0.627 0.657\n 11 2081 119 0.605 0.00788 0.590 0.621\n 12 1843 89 0.576 0.00809 0.560 0.592\n 13 1608 55 0.556 0.00823 0.540 0.573\n 14 1448 43 0.540 0.00837 0.524 0.556\n 15 1296 31 0.527 0.00848 0.511 0.544\n 16 1152 48 0.505 0.00870 0.488 0.522\n 17 1002 29 0.490 0.00886 0.473 0.508\n 18 898 21 0.479 0.00900 0.462 0.497\n 19 798 7 0.475 0.00906 0.457 0.493\n 20 705 4 0.472 0.00911 0.454 0.490\n 21 626 13 0.462 0.00932 0.444 0.481\n 22 546 8 0.455 0.00948 0.437 0.474\n 23 481 5 0.451 0.00962 0.432 0.470\n 24 436 4 0.447 0.00975 0.428 0.466\n 25 378 4 0.442 0.00993 0.423 0.462\n 26 336 3 0.438 0.01010 0.419 0.458\n 27 297 1 0.436 0.01017 0.417 0.457\n 29 235 1 0.435 0.01030 0.415 0.455\n 38 73 1 0.429 0.01175 0.406 0.452\n\n\nWhile using summary() we can add the option times and specify certain times at which we want to see the survival information\n\n#print its summary at specific times\nsummary(linelistsurv_fit, times = c(5,10,20,30,60))\n\nCall: survfit(formula = survobj ~ 1)\n\n time n.risk n.event survival std.err lower 95% CI upper 95% CI\n 5 3899 656 0.852 0.00535 0.841 0.862\n 10 2311 810 0.642 0.00761 0.627 0.657\n 20 705 446 0.472 0.00911 0.454 0.490\n 30 210 39 0.435 0.01030 0.415 0.455\n 60 2 1 0.429 0.01175 0.406 0.452\n\n\nWe can also use the print() function. The print.rmean = TRUE argument is used to obtain the mean survival time and its standard error (se).\nNOTE: The restricted mean survival time (RMST) is a specific survival measure more and more used in cancer survival analysis and which is often defined as the area under the survival curve, given we observe patients up to restricted time T (more details in Resources section).\n\n# print linelistsurv_fit object with mean survival time and its se. \nprint(linelistsurv_fit, print.rmean = TRUE)\n\nCall: survfit(formula = survobj ~ 1)\n\n n events rmean* se(rmean) median 0.95LCL 0.95UCL\n[1,] 4539 1952 33.1 0.539 17 16 18\n * restricted mean with upper limit = 64 \n\n\nTIP: We can create the surv object directly in the survfit() function and save a line of code. This will then look like: linelistsurv_quick <- survfit(Surv(futime, event) ~ 1, data=linelist_surv).\n\n\nCumulative hazard\nBesides the summary() function, we can also use the str() function that gives more details on the structure of the survfit() object. It is a list of 16 elements.\nAmong these elements is an important one: cumhaz, which is a numeric vector. This could be plotted to allow show the cumulative hazard, with the hazard being the instantaneous rate of event occurrence (see references).\n\nstr(linelistsurv_fit)\n\nList of 17\n $ n : int 4539\n $ time : num [1:59] 1 2 3 4 5 6 7 8 9 10 ...\n $ n.risk : num [1:59] 4539 4500 4394 4176 3899 ...\n $ n.event : num [1:59] 30 69 149 194 214 210 179 167 145 109 ...\n $ n.censor : num [1:59] 9 37 69 83 93 159 145 139 137 121 ...\n $ surv : num [1:59] 0.993 0.978 0.945 0.901 0.852 ...\n $ std.err : num [1:59] 0.00121 0.00222 0.00359 0.00496 0.00628 ...\n $ cumhaz : num [1:59] 0.00661 0.02194 0.05585 0.10231 0.15719 ...\n $ std.chaz : num [1:59] 0.00121 0.00221 0.00355 0.00487 0.00615 ...\n $ type : chr \"right\"\n $ logse : logi TRUE\n $ conf.int : num 0.95\n $ conf.type: chr \"log\"\n $ lower : num [1:59] 0.991 0.974 0.938 0.892 0.841 ...\n $ upper : num [1:59] 0.996 0.982 0.952 0.91 0.862 ...\n $ t0 : num 0\n $ call : language survfit(formula = survobj ~ 1)\n - attr(*, \"class\")= chr \"survfit\"\n\n\n\n\n\nPlotting Kaplan-Meir curves\nOnce the KM estimates are fitted, we can visualize the probability of being alive through a given time using the basic plot() function that draws the “Kaplan-Meier curve”. In other words, the curve below is a conventional illustration of the survival experience in the whole patient group.\nWe can quickly verify the follow-up time min and max on the curve.\nAn easy way to interpret is to say that at time zero, all the participants are still alive and survival probability is then 100%. This probability decreases over time as patients die. The proportion of participants surviving past 60 days of follow-up is around 40%.\n\nplot(linelistsurv_fit, \n xlab = \"Days of follow-up\", # x-axis label\n ylab=\"Survival Probability\", # y-axis label\n main= \"Overall survival curve\" # figure title\n )\n\n\n\n\n\n\n\n\nThe confidence interval of the KM survival estimates are also plotted by default and can be dismissed by adding the option conf.int = FALSE to the plot() command.\nSince the event of interest is “death”, drawing a curve describing the complements of the survival proportions will lead to drawing the cumulative mortality proportions. This can be done with lines(), which adds information to an existing plot.\n\n# original plot\nplot(\n linelistsurv_fit,\n xlab = \"Days of follow-up\", \n ylab = \"Survival Probability\", \n mark.time = TRUE, # mark events on the curve: a \"+\" is printed at every event\n conf.int = FALSE, # do not plot the confidence interval\n main = \"Overall survival curve and cumulative mortality\"\n )\n\n# draw an additional curve to the previous plot\nlines(\n linelistsurv_fit,\n lty = 3, # use different line type for clarity\n fun = \"event\", # draw the cumulative events instead of the survival \n mark.time = FALSE,\n conf.int = FALSE\n )\n\n# add a legend to the plot\nlegend(\n \"topright\", # position of legend\n legend = c(\"Survival\", \"Cum. Mortality\"), # legend text \n lty = c(1, 3), # line types to use in the legend\n cex = .85, # parametes that defines size of legend text\n bty = \"n\" # no box type to be drawn for the legend\n )", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2375,7 +2375,7 @@ "href": "new_pages/survival_analysis.html#comparison-of-survival-curves", "title": "27  Survival analysis", "section": "27.4 Comparison of survival curves", - "text": "27.4 Comparison of survival curves\nTo compare the survival within different groups of our observed participants or patients, we might need to first look at their respective survival curves and then run tests to evaluate the difference between independent groups. This comparison can concern groups based on gender, age, treatment, comorbidity…\n\nLog rank test\nThe log rank test is a popular test that compares the entire survival experience between two or more independent groups and can be thought of as a test of whether the survival curves are identical (overlapping) or not (null hypothesis of no difference in survival between the groups). The survdiff() function of the survival package allows running the log-rank test when we specify rho = 0 (which is the default). The test results gives a chi-square statistic along with a p-value since the log rank statistic is approximately distributed as a chi-square test statistic.\nWe first try to compare the survival curves by gender group. For this, we first try to visualize it (check whether the two survival curves are overlapping). A new survfit object will be created with a slightly different formula. Then the survdiff object will be created.\nBy supplying ~ gender as the right side of the formula, we no longer plot the overall survival but instead by gender.\n\n# create the new survfit object based on gender\nlinelistsurv_fit_sex <- survfit(Surv(futime, event) ~ gender, data = linelist_surv)\n\nNow we can plot the survival curves by gender. Have a look at the order of the strata levels in the gender column before defining your colors and legend.\n\n# set colors\ncol_sex <- c(\"lightgreen\", \"darkgreen\")\n\n# create plot\nplot(\n linelistsurv_fit_sex,\n col = col_sex,\n xlab = \"Days of follow-up\",\n ylab = \"Survival Probability\")\n\n# add legend\nlegend(\n \"topright\",\n legend = c(\"Female\",\"Male\"),\n col = col_sex,\n lty = 1,\n cex = .9,\n bty = \"n\")\n\n\n\n\n\n\n\n\nAnd now we can compute the test of the difference between the survival curves using survdiff()\n\n#compute the test of the difference between the survival curves\nsurvival::survdiff(\n Surv(futime, event) ~ gender, \n data = linelist_surv\n )\n\nCall:\nsurvival::survdiff(formula = Surv(futime, event) ~ gender, data = linelist_surv)\n\nn=4321, 218 observations deleted due to missingness.\n\n N Observed Expected (O-E)^2/E (O-E)^2/V\ngender=f 2156 924 909 0.255 0.524\ngender=m 2165 929 944 0.245 0.524\n\n Chisq= 0.5 on 1 degrees of freedom, p= 0.5 \n\n\nWe see that the survival curve for women and the one for men overlap and the log-rank test does not give evidence of a survival difference between women and men.\nSome other R packages allow illustrating survival curves for different groups and testing the difference all at once. Using the ggsurvplot() function from the survminer package, we can also include in our curve the printed risk tables for each group, as well the p-value from the log-rank test.\nCAUTION: survminer functions require that you specify the survival object and again specify the data used to fit the survival object. Remember to do this to avoid non-specific error messages. \n\nsurvminer::ggsurvplot(\n linelistsurv_fit_sex, \n data = linelist_surv, # again specify the data used to fit linelistsurv_fit_sex \n conf.int = FALSE, # do not show confidence interval of KM estimates\n surv.scale = \"percent\", # present probabilities in the y axis in %\n break.time.by = 10, # present the time axis with an increment of 10 days\n xlab = \"Follow-up days\",\n ylab = \"Survival Probability\",\n pval = T, # print p-value of Log-rank test \n pval.coord = c(40,.91), # print p-value at these plot coordinates\n risk.table = T, # print the risk table at bottom \n legend.title = \"Gender\", # legend characteristics\n legend.labs = c(\"Female\",\"Male\"),\n font.legend = 10, \n palette = \"Dark2\", # specify color palette \n surv.median.line = \"hv\", # draw horizontal and vertical lines to the median survivals\n ggtheme = theme_light() # simplify plot background\n)\n\n\n\n\n\n\n\n\nWe may also want to test for differences in survival by the source of infection (source of contamination).\nIn this case, the Log rank test gives enough evidence of a difference in the survival probabilities at alpha= 0.005. The survival probabilities for patients that were infected at funerals are higher than the survival probabilities for patients that got infected in other places, suggesting a survival benefit.\n\nlinelistsurv_fit_source <- survfit(\n Surv(futime, event) ~ source,\n data = linelist_surv\n )\n\n# plot\nggsurvplot( \n linelistsurv_fit_source,\n data = linelist_surv,\n size = 1, linetype = \"strata\", # line types\n conf.int = T,\n surv.scale = \"percent\", \n break.time.by = 10, \n xlab = \"Follow-up days\",\n ylab= \"Survival Probability\",\n pval = T,\n pval.coord = c(40,.91),\n risk.table = T,\n legend.title = \"Source of \\ninfection\",\n legend.labs = c(\"Funeral\", \"Other\"),\n font.legend = 10,\n palette = c(\"#E7B800\",\"#3E606F\"),\n surv.median.line = \"hv\", \n ggtheme = theme_light()\n)\n\nWarning in geom_segment(aes(x = 0, y = max(y2), xend = max(x1), yend = max(y2)), : All aesthetics have length 1, but the data has 2 rows.\nℹ Did you mean to use `annotate()`?\nAll aesthetics have length 1, but the data has 2 rows.\nℹ Did you mean to use `annotate()`?\nAll aesthetics have length 1, but the data has 2 rows.\nℹ Did you mean to use `annotate()`?\nAll aesthetics have length 1, but the data has 2 rows.\nℹ Did you mean to use `annotate()`?", + "text": "27.4 Comparison of survival curves\nTo compare the survival within different groups of our observed participants or patients, we might need to first look at their respective survival curves and then run tests to evaluate the difference between independent groups. This comparison can concern groups based on gender, age, treatment, comorbidity, etc.\n\nLog rank test\nThe log rank test is a popular test that compares the entire survival experience between two or more independent groups and can be thought of as a test of whether the survival curves are identical (overlapping) or not (null hypothesis of no difference in survival between the groups). The survdiff() function of the survival package allows running the log-rank test when we specify rho = 0 (which is the default). The test results gives a chi-square statistic along with a p-value since the log rank statistic is approximately distributed as a chi-square test statistic.\nWe first try to compare the survival curves by gender group. For this, we first try to visualize it (check whether the two survival curves are overlapping). A new survfit object will be created with a slightly different formula. Then the survdiff object will be created.\nBy supplying ~ gender as the right side of the formula, we no longer plot the overall survival but instead by gender.\n\n# create the new survfit object based on gender\nlinelistsurv_fit_sex <- survfit(Surv(futime, event) ~ gender, data = linelist_surv)\n\nNow we can plot the survival curves by gender. Have a look at the order of the strata levels in the gender column before defining your colors and legend.\n\n# set colors\ncol_sex <- c(\"lightgreen\", \"darkgreen\")\n\n# create plot\nplot(\n linelistsurv_fit_sex,\n col = col_sex,\n xlab = \"Days of follow-up\",\n ylab = \"Survival Probability\")\n\n# add legend\nlegend(\n \"topright\",\n legend = c(\"Female\",\"Male\"),\n col = col_sex,\n lty = 1,\n cex = .9,\n bty = \"n\")\n\n\n\n\n\n\n\n\nAnd now we can compute the test of the difference between the survival curves using survdiff()\n\n#compute the test of the difference between the survival curves\nsurvival::survdiff(\n Surv(futime, event) ~ gender, \n data = linelist_surv\n )\n\nCall:\nsurvival::survdiff(formula = Surv(futime, event) ~ gender, data = linelist_surv)\n\nn=4321, 218 observations deleted due to missingness.\n\n N Observed Expected (O-E)^2/E (O-E)^2/V\ngender=f 2156 924 909 0.255 0.524\ngender=m 2165 929 944 0.245 0.524\n\n Chisq= 0.5 on 1 degrees of freedom, p= 0.5 \n\n\nWe see that the survival curve for women and the one for men overlap and the log-rank test does not give evidence of a survival difference between women and men.\nSome other R packages allow illustrating survival curves for different groups and testing the difference all at once. Using the ggsurvplot() function from the survminer package, we can also include in our curve the printed risk tables for each group, as well the p-value from the log-rank test.\nCAUTION: survminer functions require that you specify the survival object and again specify the data used to fit the survival object. Remember to do this to avoid non-specific error messages. \n\nsurvminer::ggsurvplot(\n linelistsurv_fit_sex, \n data = linelist_surv, # again specify the data used to fit linelistsurv_fit_sex \n conf.int = FALSE, # do not show confidence interval of KM estimates\n surv.scale = \"percent\", # present probabilities in the y axis in %\n break.time.by = 10, # present the time axis with an increment of 10 days\n xlab = \"Follow-up days\",\n ylab = \"Survival Probability\",\n pval = T, # print p-value of Log-rank test \n pval.coord = c(40,.91), # print p-value at these plot coordinates\n risk.table = T, # print the risk table at bottom \n legend.title = \"Gender\", # legend characteristics\n legend.labs = c(\"Female\",\"Male\"),\n font.legend = 10, \n palette = \"Dark2\", # specify color palette \n surv.median.line = \"hv\", # draw horizontal and vertical lines to the median survivals\n ggtheme = theme_light() # simplify plot background\n)\n\n\n\n\n\n\n\n\nWe may also want to test for differences in survival by the source of infection (source of contamination).\nIn this case, the Log rank test gives enough evidence of a difference in the survival probabilities at alpha= 0.005. The survival probabilities for patients that were infected at funerals are higher than the survival probabilities for patients that got infected in other places, suggesting a survival benefit.\n\nlinelistsurv_fit_source <- survfit(\n Surv(futime, event) ~ source,\n data = linelist_surv\n )\n\n# plot\nggsurvplot( \n linelistsurv_fit_source,\n data = linelist_surv,\n size = 1, linetype = \"strata\", # line types\n conf.int = T,\n surv.scale = \"percent\", \n break.time.by = 10, \n xlab = \"Follow-up days\",\n ylab= \"Survival Probability\",\n pval = T,\n pval.coord = c(40,.91),\n risk.table = T,\n legend.title = \"Source of \\ninfection\",\n legend.labs = c(\"Funeral\", \"Other\"),\n font.legend = 10,\n palette = c(\"#E7B800\",\"#3E606F\"),\n surv.median.line = \"hv\", \n ggtheme = theme_light()\n)\n\nWarning in geom_segment(aes(x = 0, y = max(y2), xend = max(x1), yend = max(y2)), : All aesthetics have length 1, but the data has 2 rows.\nℹ Please consider using `annotate()` or provide this layer with data containing\n a single row.\nAll aesthetics have length 1, but the data has 2 rows.\nℹ Please consider using `annotate()` or provide this layer with data containing\n a single row.\nAll aesthetics have length 1, but the data has 2 rows.\nℹ Please consider using `annotate()` or provide this layer with data containing\n a single row.\nAll aesthetics have length 1, but the data has 2 rows.\nℹ Please consider using `annotate()` or provide this layer with data containing\n a single row.", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2386,7 +2386,7 @@ "href": "new_pages/survival_analysis.html#cox-regression-analysis", "title": "27  Survival analysis", "section": "27.5 Cox regression analysis", - "text": "27.5 Cox regression analysis\nCox proportional hazards regression is one of the most popular regression techniques for survival analysis. Other models can also be used since the Cox model requires important assumptions that need to be verified for an appropriate use such as the proportional hazards assumption: see references.\nIn a Cox proportional hazards regression model, the measure of effect is the hazard rate (HR), which is the risk of failure (or the risk of death in our example), given that the participant has survived up to a specific time. Usually, we are interested in comparing independent groups with respect to their hazards, and we use a hazard ratio, which is analogous to an odds ratio in the setting of multiple logistic regression analysis. The cox.ph() function from the survival package is used to fit the model. The function cox.zph() from survival package may be used to test the proportional hazards assumption for a Cox regression model fit.\nNOTE: A probability must lie in the range 0 to 1. However, the hazard represents the expected number of events per one unit of time.\n\nIf the hazard ratio for a predictor is close to 1 then that predictor does not affect survival,\nif the HR is less than 1, then the predictor is protective (i.e., associated with improved survival),\nand if the HR is greater than 1, then the predictor is associated with increased risk (or decreased survival).\n\n\nFitting a Cox model\nWe can first fit a model to assess the effect of age and gender on the survival. By just printing the model, we have the information on:\n\nthe estimated regression coefficients coef which quantifies the association between the predictors and the outcome,\ntheir exponential (for interpretability, exp(coef)) which produces the hazard ratio,\ntheir standard error se(coef),\nthe z-score: how many standard errors is the estimated coefficient away from 0,\nand the p-value: the probability that the estimated coefficient could be 0.\n\nThe summary() function applied to the cox model object gives more information, such as the confidence interval of the estimated HR and the different test scores.\nThe effect of the first covariate gender is presented in the first row. genderm (male) is printed, implying that the first strata level (“f”), i.e the female group, is the reference group for the gender. Thus the interpretation of the test parameter is that of men compared to women. The p-value indicates there was not enough evidence of an effect of the gender on the expected hazard or of an association between gender and all-cause mortality.\nThe same lack of evidence is noted regarding age-group.\n\n#fitting the cox model\nlinelistsurv_cox_sexage <- survival::coxph(\n Surv(futime, event) ~ gender + age_cat_small, \n data = linelist_surv\n )\n\n\n#printing the model fitted\nlinelistsurv_cox_sexage\n\nCall:\nsurvival::coxph(formula = Surv(futime, event) ~ gender + age_cat_small, \n data = linelist_surv)\n\n coef exp(coef) se(coef) z p\ngenderm -0.03149 0.96900 0.04767 -0.661 0.509\nage_cat_small5-19 0.09400 1.09856 0.06454 1.456 0.145\nage_cat_small20+ 0.05032 1.05161 0.06953 0.724 0.469\n\nLikelihood ratio test=2.8 on 3 df, p=0.4243\nn= 4321, number of events= 1853 \n (218 observations deleted due to missingness)\n\n#summary of the model\nsummary(linelistsurv_cox_sexage)\n\nCall:\nsurvival::coxph(formula = Surv(futime, event) ~ gender + age_cat_small, \n data = linelist_surv)\n\n n= 4321, number of events= 1853 \n (218 observations deleted due to missingness)\n\n coef exp(coef) se(coef) z Pr(>|z|)\ngenderm -0.03149 0.96900 0.04767 -0.661 0.509\nage_cat_small5-19 0.09400 1.09856 0.06454 1.456 0.145\nage_cat_small20+ 0.05032 1.05161 0.06953 0.724 0.469\n\n exp(coef) exp(-coef) lower .95 upper .95\ngenderm 0.969 1.0320 0.8826 1.064\nage_cat_small5-19 1.099 0.9103 0.9680 1.247\nage_cat_small20+ 1.052 0.9509 0.9176 1.205\n\nConcordance= 0.514 (se = 0.007 )\nLikelihood ratio test= 2.8 on 3 df, p=0.4\nWald test = 2.78 on 3 df, p=0.4\nScore (logrank) test = 2.78 on 3 df, p=0.4\n\n\nIt was interesting to run the model and look at the results but a first look to verify whether the proportional hazards assumptions is respected could help saving time.\n\ntest_ph_sexage <- survival::cox.zph(linelistsurv_cox_sexage)\ntest_ph_sexage\n\n chisq df p\ngender 0.454 1 0.50\nage_cat_small 0.838 2 0.66\nGLOBAL 1.399 3 0.71\n\n\nNOTE: A second argument called method can be specified when computing the cox model, that determines how ties are handled. The default is “efron”, and the other options are “breslow” and “exact”.\nIn another model we add more risk factors such as the source of infection and the number of days between date of onset and admission. This time, we first verify the proportional hazards assumption before going forward.\nIn this model, we have included a continuous predictor (days_onset_hosp). In this case we interpret the parameter estimates as the increase in the expected log of the relative hazard for each one unit increase in the predictor, holding other predictors constant. We first verify the proportional hazards assumption.\n\n#fit the model\nlinelistsurv_cox <- coxph(\n Surv(futime, event) ~ gender + age_years+ source + days_onset_hosp,\n data = linelist_surv\n )\n\n\n#test the proportional hazard model\nlinelistsurv_ph_test <- cox.zph(linelistsurv_cox)\nlinelistsurv_ph_test\n\n chisq df p\ngender 0.45062 1 0.50\nage_years 0.00199 1 0.96\nsource 1.79622 1 0.18\ndays_onset_hosp 31.66167 1 1.8e-08\nGLOBAL 34.08502 4 7.2e-07\n\n\nThe graphical verification of this assumption may be performed with the function ggcoxzph() from the survminer package.\n\nsurvminer::ggcoxzph(linelistsurv_ph_test)\n\n\n\n\n\n\n\n\nThe model results indicate there is a negative association between onset to admission duration and all-cause mortality. The expected hazard is 0.9 times lower in a person who who is one day later admitted than another, holding gender constant. Or in a more straightforward explanation, a one unit increase in the duration of onset to admission is associated with a 10.7% (coef *100) decrease in the risk of death.\nResults show also a positive association between the source of infection and the all-cause mortality. Which is to say there is an increased risk of death (1.21x) for patients that got a source of infection other than funerals.\n\n#print the summary of the model\nsummary(linelistsurv_cox)\n\nCall:\ncoxph(formula = Surv(futime, event) ~ gender + age_years + source + \n days_onset_hosp, data = linelist_surv)\n\n n= 2772, number of events= 1180 \n (1767 observations deleted due to missingness)\n\n coef exp(coef) se(coef) z Pr(>|z|) \ngenderm 0.004710 1.004721 0.060827 0.077 0.9383 \nage_years -0.002249 0.997753 0.002421 -0.929 0.3528 \nsourceother 0.178393 1.195295 0.084291 2.116 0.0343 * \ndays_onset_hosp -0.104063 0.901169 0.014245 -7.305 2.77e-13 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n exp(coef) exp(-coef) lower .95 upper .95\ngenderm 1.0047 0.9953 0.8918 1.1319\nage_years 0.9978 1.0023 0.9930 1.0025\nsourceother 1.1953 0.8366 1.0133 1.4100\ndays_onset_hosp 0.9012 1.1097 0.8764 0.9267\n\nConcordance= 0.566 (se = 0.009 )\nLikelihood ratio test= 71.31 on 4 df, p=1e-14\nWald test = 59.22 on 4 df, p=4e-12\nScore (logrank) test = 59.54 on 4 df, p=4e-12\n\n\nWe can verify this relationship with a table:\n\nlinelist_case_data %>% \n tabyl(days_onset_hosp, outcome) %>% \n adorn_percentages() %>% \n adorn_pct_formatting()\n\n days_onset_hosp Death Recover NA_\n 0 44.3% 31.4% 24.3%\n 1 46.6% 32.2% 21.2%\n 2 43.0% 32.8% 24.2%\n 3 45.0% 32.3% 22.7%\n 4 41.5% 38.3% 20.2%\n 5 40.0% 36.2% 23.8%\n 6 32.2% 48.7% 19.1%\n 7 31.8% 38.6% 29.5%\n 8 29.8% 38.6% 31.6%\n 9 30.3% 51.5% 18.2%\n 10 16.7% 58.3% 25.0%\n 11 36.4% 45.5% 18.2%\n 12 18.8% 62.5% 18.8%\n 13 10.0% 60.0% 30.0%\n 14 10.0% 50.0% 40.0%\n 15 28.6% 42.9% 28.6%\n 16 20.0% 80.0% 0.0%\n 17 0.0% 100.0% 0.0%\n 18 0.0% 100.0% 0.0%\n 22 0.0% 100.0% 0.0%\n NA 52.7% 31.2% 16.0%\n\n\nWe would need to consider and investigate why this association exists in the data. One possible explanation could be that patients who live long enough to be admitted later had less severe disease to begin with. Another perhaps more likely explanation is that since we used a simulated fake dataset, this pattern does not reflect reality!\n\n\n\nForest plots\nWe can then visualize the results of the cox model using the practical forest plots with the ggforest() function of the survminer package.\n\nggforest(linelistsurv_cox, data = linelist_surv)", + "text": "27.5 Cox regression analysis\nCox proportional hazards regression is one of the most popular regression techniques for survival analysis. Other models can also be used since the Cox model requires important assumptions that need to be verified for an appropriate use such as the proportional hazards assumption: see references.\nIn a Cox proportional hazards regression model, the measure of effect is the hazard rate (HR), which is the risk of failure (or the risk of death in our example), given that the participant has survived up to a specific time. Usually, we are interested in comparing independent groups with respect to their hazards, and we use a hazard ratio, which is analogous to an odds ratio in the setting of multiple logistic regression analysis. The cox.ph() function from the survival package is used to fit the model. The function cox.zph() from survival package may be used to test the proportional hazards assumption for a Cox regression model fit.\nNOTE: A probability must lie in the range 0 to 1. However, the hazard represents the expected number of events per one unit of time.\n\nIf the hazard ratio for a predictor is close to 1 then that predictor does not affect survival,\nif the HR is less than 1, then the predictor is protective (i.e., associated with improved survival),\nand if the HR is greater than 1, then the predictor is associated with increased risk (or decreased survival).\n\n\nFitting a Cox model\nWe can first fit a model to assess the effect of age and gender on the survival. By just printing the model, we have the information on:\n\nThe estimated regression coefficients coef which quantifies the association between the predictors and the outcome,\ntheir exponential (for interpretability, exp(coef)) which produces the hazard ratio,\ntheir standard error se(coef),\nthe z-score: how many standard errors is the estimated coefficient away from 0,\nand the p-value: the probability that the estimated coefficient could be 0.\n\nThe summary() function applied to the cox model object gives more information, such as the confidence interval of the estimated HR and the different test scores.\nThe effect of the first covariate gender is presented in the first row. genderm (male) is printed, implying that the first strata level (“f”), i.e the female group, is the reference group for the gender. Thus the interpretation of the test parameter is that of men compared to women. The p-value indicates there was not enough evidence of an effect of the gender on the expected hazard or of an association between gender and all-cause mortality.\nThe same lack of evidence is noted regarding age-group.\n\n#fitting the cox model\nlinelistsurv_cox_sexage <- survival::coxph(\n Surv(futime, event) ~ gender + age_cat_small, \n data = linelist_surv\n )\n\n\n#printing the model fitted\nlinelistsurv_cox_sexage\n\nCall:\nsurvival::coxph(formula = Surv(futime, event) ~ gender + age_cat_small, \n data = linelist_surv)\n\n coef exp(coef) se(coef) z p\ngenderm -0.03149 0.96900 0.04767 -0.661 0.509\nage_cat_small5-19 0.09400 1.09856 0.06454 1.456 0.145\nage_cat_small20+ 0.05032 1.05161 0.06953 0.724 0.469\n\nLikelihood ratio test=2.8 on 3 df, p=0.4243\nn= 4321, number of events= 1853 \n (218 observations deleted due to missingness)\n\n#summary of the model\nsummary(linelistsurv_cox_sexage)\n\nCall:\nsurvival::coxph(formula = Surv(futime, event) ~ gender + age_cat_small, \n data = linelist_surv)\n\n n= 4321, number of events= 1853 \n (218 observations deleted due to missingness)\n\n coef exp(coef) se(coef) z Pr(>|z|)\ngenderm -0.03149 0.96900 0.04767 -0.661 0.509\nage_cat_small5-19 0.09400 1.09856 0.06454 1.456 0.145\nage_cat_small20+ 0.05032 1.05161 0.06953 0.724 0.469\n\n exp(coef) exp(-coef) lower .95 upper .95\ngenderm 0.969 1.0320 0.8826 1.064\nage_cat_small5-19 1.099 0.9103 0.9680 1.247\nage_cat_small20+ 1.052 0.9509 0.9176 1.205\n\nConcordance= 0.514 (se = 0.007 )\nLikelihood ratio test= 2.8 on 3 df, p=0.4\nWald test = 2.78 on 3 df, p=0.4\nScore (logrank) test = 2.78 on 3 df, p=0.4\n\n\nIt was interesting to run the model and look at the results but a first look to verify whether the proportional hazards assumptions is respected could help saving time.\n\ntest_ph_sexage <- survival::cox.zph(linelistsurv_cox_sexage)\ntest_ph_sexage\n\n chisq df p\ngender 0.454 1 0.50\nage_cat_small 0.838 2 0.66\nGLOBAL 1.399 3 0.71\n\n\nNOTE: A second argument called method can be specified when computing the cox model, that determines how ties are handled. The default is “efron”, and the other options are “breslow” and “exact”.\nIn another model we add more risk factors such as the source of infection and the number of days between date of onset and admission. This time, we first verify the proportional hazards assumption before going forward.\nIn this model, we have included a continuous predictor (days_onset_hosp). In this case we interpret the parameter estimates as the increase in the expected log of the relative hazard for each one unit increase in the predictor, holding other predictors constant. We first verify the proportional hazards assumption.\n\n#fit the model\nlinelistsurv_cox <- coxph(\n Surv(futime, event) ~ gender + age_years+ source + days_onset_hosp,\n data = linelist_surv\n )\n\n\n#test the proportional hazard model\nlinelistsurv_ph_test <- cox.zph(linelistsurv_cox)\nlinelistsurv_ph_test\n\n chisq df p\ngender 0.45062 1 0.50\nage_years 0.00199 1 0.96\nsource 1.79622 1 0.18\ndays_onset_hosp 31.66167 1 1.8e-08\nGLOBAL 34.08502 4 7.2e-07\n\n\nThe graphical verification of this assumption may be performed with the function ggcoxzph() from the survminer package.\n\nsurvminer::ggcoxzph(linelistsurv_ph_test)\n\n\n\n\n\n\n\n\nThe model results indicate there is a negative association between onset to admission duration and all-cause mortality. The expected hazard is 0.9 times lower in a person who who is one day later admitted than another, holding gender constant. Or in a more straightforward explanation, a one unit increase in the duration of onset to admission is associated with a 10.7% (coef *100) decrease in the risk of death.\nResults show also a positive association between the source of infection and the all-cause mortality. Which is to say there is an increased risk of death (1.21x) for patients that got a source of infection other than funerals.\n\n#print the summary of the model\nsummary(linelistsurv_cox)\n\nCall:\ncoxph(formula = Surv(futime, event) ~ gender + age_years + source + \n days_onset_hosp, data = linelist_surv)\n\n n= 2772, number of events= 1180 \n (1767 observations deleted due to missingness)\n\n coef exp(coef) se(coef) z Pr(>|z|) \ngenderm 0.004710 1.004721 0.060827 0.077 0.9383 \nage_years -0.002249 0.997753 0.002421 -0.929 0.3528 \nsourceother 0.178393 1.195295 0.084291 2.116 0.0343 * \ndays_onset_hosp -0.104063 0.901169 0.014245 -7.305 2.77e-13 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n exp(coef) exp(-coef) lower .95 upper .95\ngenderm 1.0047 0.9953 0.8918 1.1319\nage_years 0.9978 1.0023 0.9930 1.0025\nsourceother 1.1953 0.8366 1.0133 1.4100\ndays_onset_hosp 0.9012 1.1097 0.8764 0.9267\n\nConcordance= 0.566 (se = 0.009 )\nLikelihood ratio test= 71.31 on 4 df, p=1e-14\nWald test = 59.22 on 4 df, p=4e-12\nScore (logrank) test = 59.54 on 4 df, p=4e-12\n\n\nWe can verify this relationship with a table:\n\nlinelist_case_data %>% \n tabyl(days_onset_hosp, outcome) %>% \n adorn_percentages() %>% \n adorn_pct_formatting()\n\n days_onset_hosp Death Recover NA_\n 0 44.3% 31.4% 24.3%\n 1 46.6% 32.2% 21.2%\n 2 43.0% 32.8% 24.2%\n 3 45.0% 32.3% 22.7%\n 4 41.5% 38.3% 20.2%\n 5 40.0% 36.2% 23.8%\n 6 32.2% 48.7% 19.1%\n 7 31.8% 38.6% 29.5%\n 8 29.8% 38.6% 31.6%\n 9 30.3% 51.5% 18.2%\n 10 16.7% 58.3% 25.0%\n 11 36.4% 45.5% 18.2%\n 12 18.8% 62.5% 18.8%\n 13 10.0% 60.0% 30.0%\n 14 10.0% 50.0% 40.0%\n 15 28.6% 42.9% 28.6%\n 16 20.0% 80.0% 0.0%\n 17 0.0% 100.0% 0.0%\n 18 0.0% 100.0% 0.0%\n 22 0.0% 100.0% 0.0%\n NA 52.7% 31.2% 16.0%\n\n\nWe would need to consider and investigate why this association exists in the data. One possible explanation could be that patients who live long enough to be admitted later had less severe disease to begin with. Another perhaps more likely explanation is that since we used a simulated fake dataset, this pattern does not reflect reality!\n\n\n\nForest plots\nWe can then visualize the results of the cox model using the practical forest plots with the ggforest() function of the survminer package.\n\nggforest(linelistsurv_cox, data = linelist_surv)", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2397,7 +2397,7 @@ "href": "new_pages/survival_analysis.html#time-dependent-covariates-in-survival-models", "title": "27  Survival analysis", "section": "27.6 Time-dependent covariates in survival models", - "text": "27.6 Time-dependent covariates in survival models\nSome of the following sections have been adapted with permission from an excellent introduction to survival analysis in R by Dr. Emily Zabor\nIn the last section we covered using Cox regression to examine associations between covariates of interest and survival outcomes.But these analyses rely on the covariate being measured at baseline, that is, before follow-up time for the event begins.\nWhat happens if you are interested in a covariate that is measured after follow-up time begins? Or, what if you have a covariate that can change over time?\nFor example, maybe you are working with clinical data where you repeated measures of hospital laboratory values that can change over time. This is an example of a Time Dependent Covariate. In order to address this you need a special setup, but fortunately the cox model is very flexible and this type of data can also be modeled with tools from the survival package.\n\nTime-dependent covariate setup\nAnalysis of time-dependent covariates in R requires setup of a special dataset. If interested, see the more detailed paper on this by the author of the survival package Using Time Dependent Covariates and Time Dependent Coefficients in the Cox Model.\nFor this, we’ll use a new dataset from the SemiCompRisks package named BMT, which includes data on 137 bone marrow transplant patients. The variables we’ll focus on are:\n\nT1 - time (in days) to death or last follow-up\n\ndelta1 - death indicator; 1-Dead, 0-Alive\n\nTA - time (in days) to acute graft-versus-host disease\n\ndeltaA - acute graft-versus-host disease indicator;\n\n1 - Developed acute graft-versus-host disease\n\n0 - Never developed acute graft-versus-host disease\n\n\nWe’ll load this dataset from the survival package using the base R command data(), which can be used for loading data that is already included in a R package that is loaded. The data frame BMT will appear in your R environment.\n\ndata(BMT, package = \"SemiCompRisks\")\n\n\nAdd unique patient identifier\nThere is no unique ID column in the BMT data, which is needed to create the type of dataset we want. So we use the function rowid_to_column() from the tidyverse package tibble to create a new id column called my_id (adds column at start of data frame with sequential row ids, starting at 1). We name the data frame bmt.\n\nbmt <- rowid_to_column(BMT, \"my_id\")\n\nThe dataset now looks like this:\n\n\n\n\n\n\n\n\nExpand patient rows\nNext, we’ll use the tmerge() function with the event() and tdc() helper functions to create the restructured dataset. Our goal is to restructure the dataset to create a separate row for each patient for each time interval where they have a different value for deltaA. In this case, each patient can have at most two rows depending on whether they developed acute graft-versus-host disease during the data collection period. We’ll call our new indicator for the development of acute graft-versus-host disease agvhd.\n\ntmerge() creates a long dataset with multiple time intervals for the different covariate values for each patient\nevent() creates the new event indicator to go with the newly-created time intervals\ntdc() creates the time-dependent covariate column, agvhd, to go with the newly created time intervals\n\n\ntd_dat <- \n tmerge(\n data1 = bmt %>% select(my_id, T1, delta1), \n data2 = bmt %>% select(my_id, T1, delta1, TA, deltaA), \n id = my_id, \n death = event(T1, delta1),\n agvhd = tdc(TA)\n )\n\nTo see what this does, let’s look at the data for the first 5 individual patients.\nThe variables of interest in the original data looked like this:\n\nbmt %>% \n select(my_id, T1, delta1, TA, deltaA) %>% \n filter(my_id %in% seq(1, 5))\n\n my_id T1 delta1 TA deltaA\n1 1 2081 0 67 1\n2 2 1602 0 1602 0\n3 3 1496 0 1496 0\n4 4 1462 0 70 1\n5 5 1433 0 1433 0\n\n\nThe new dataset for these same patients looks like this:\n\ntd_dat %>% \n filter(my_id %in% seq(1, 5))\n\n my_id T1 delta1 tstart tstop death agvhd\n1 1 2081 0 0 67 0 0\n2 1 2081 0 67 2081 0 1\n3 2 1602 0 0 1602 0 0\n4 3 1496 0 0 1496 0 0\n5 4 1462 0 0 70 0 0\n6 4 1462 0 70 1462 0 1\n7 5 1433 0 0 1433 0 0\n\n\nNow some of our patients have two rows in the dataset corresponding to intervals where they have a different value of our new variable, agvhd. For example, Patient 1 now has two rows with a agvhd value of zero from time 0 to time 67, and a value of 1 from time 67 to time 2081.\n\n\n\nCox regression with time-dependent covariates\nNow that we’ve reshaped our data and added the new time-dependent aghvd variable, let’s fit a simple single variable cox regression model. We can use the same coxph() function as before, we just need to change our Surv() function to specify both the start and stop time for each interval using the time1 = and time2 = arguments.\n\nbmt_td_model = coxph(\n Surv(time = tstart, time2 = tstop, event = death) ~ agvhd, \n data = td_dat\n )\n\nsummary(bmt_td_model)\n\nCall:\ncoxph(formula = Surv(time = tstart, time2 = tstop, event = death) ~ \n agvhd, data = td_dat)\n\n n= 163, number of events= 80 \n\n coef exp(coef) se(coef) z Pr(>|z|)\nagvhd 0.3351 1.3980 0.2815 1.19 0.234\n\n exp(coef) exp(-coef) lower .95 upper .95\nagvhd 1.398 0.7153 0.8052 2.427\n\nConcordance= 0.535 (se = 0.024 )\nLikelihood ratio test= 1.33 on 1 df, p=0.2\nWald test = 1.42 on 1 df, p=0.2\nScore (logrank) test = 1.43 on 1 df, p=0.2\n\n\nAgain, we’ll visualize our cox model results using the ggforest() function from the survminer package.:\n\nggforest(bmt_td_model, data = td_dat)\n\n\n\n\n\n\n\n\nAs you can see from the forest plot, confidence interval, and p-value, there does not appear to be a strong association between death and acute graft-versus-host disease in the context of our simple model.", + "text": "27.6 Time-dependent covariates in survival models\nSome of the following sections have been adapted with permission from an excellent introduction to survival analysis in R by Dr. Emily Zabor.\nIn the last section we covered using Cox regression to examine associations between covariates of interest and survival outcomes.But these analyses rely on the covariate being measured at baseline, that is, before follow-up time for the event begins.\nWhat happens if you are interested in a covariate that is measured after follow-up time begins? Or, what if you have a covariate that can change over time?\nFor example, maybe you are working with clinical data where you repeated measures of hospital laboratory values that can change over time. This is an example of a Time Dependent Covariate. In order to address this you need a special setup, but fortunately the cox model is very flexible and this type of data can also be modeled with tools from the survival package.\n\nTime-dependent covariate setup\nAnalysis of time-dependent covariates in R requires setup of a special dataset. If interested, see the more detailed paper on this by the author of the survival package Using Time Dependent Covariates and Time Dependent Coefficients in the Cox Model.\nFor this, we’ll use a new dataset from the SemiCompRisks package named BMT, which includes data on 137 bone marrow transplant patients. The variables we’ll focus on are:\n\nT1 - time (in days) to death or last follow-up.\n\ndelta1 - death indicator; 1-Dead, 0-Alive.\n\nTA - time (in days) to acute graft-versus-host disease.\n\ndeltaA - acute graft-versus-host disease indicator;\n\n1 - Developed acute graft-versus-host disease.\n\n0 - Never developed acute graft-versus-host disease.\n\n\nWe’ll load this dataset from the survival package using the base R command data(), which can be used for loading data that is already included in a R package that is loaded. The data frame BMT will appear in your R environment.\n\ndata(BMT, package = \"SemiCompRisks\")\n\n\nAdd unique patient identifier\nThere is no unique ID column in the BMT data, which is needed to create the type of dataset we want. So we use the function rowid_to_column() from the tidyverse package tibble to create a new id column called my_id (adds column at start of data frame with sequential row ids, starting at 1). We name the data frame bmt.\n\nbmt <- rowid_to_column(BMT, \"my_id\")\n\nThe dataset now looks like this:\n\n\n\n\n\n\n\n\nExpand patient rows\nNext, we’ll use the tmerge() function with the event() and tdc() helper functions to create the restructured dataset. Our goal is to restructure the dataset to create a separate row for each patient for each time interval where they have a different value for deltaA. In this case, each patient can have at most two rows depending on whether they developed acute graft-versus-host disease during the data collection period. We’ll call our new indicator for the development of acute graft-versus-host disease agvhd.\n\ntmerge() creates a long dataset with multiple time intervals for the different covariate values for each patient.\nevent() creates the new event indicator to go with the newly-created time intervals.\ntdc() creates the time-dependent covariate column, agvhd, to go with the newly created time intervals.\n\n\ntd_dat <- \n tmerge(\n data1 = bmt %>% select(my_id, T1, delta1), \n data2 = bmt %>% select(my_id, T1, delta1, TA, deltaA), \n id = my_id, \n death = event(T1, delta1),\n agvhd = tdc(TA)\n )\n\nTo see what this does, let’s look at the data for the first 5 individual patients.\nThe variables of interest in the original data looked like this:\n\nbmt %>% \n select(my_id, T1, delta1, TA, deltaA) %>% \n filter(my_id %in% seq(1, 5))\n\n my_id T1 delta1 TA deltaA\n1 1 2081 0 67 1\n2 2 1602 0 1602 0\n3 3 1496 0 1496 0\n4 4 1462 0 70 1\n5 5 1433 0 1433 0\n\n\nThe new dataset for these same patients looks like this:\n\ntd_dat %>% \n filter(my_id %in% seq(1, 5))\n\n my_id T1 delta1 tstart tstop death agvhd\n1 1 2081 0 0 67 0 0\n2 1 2081 0 67 2081 0 1\n3 2 1602 0 0 1602 0 0\n4 3 1496 0 0 1496 0 0\n5 4 1462 0 0 70 0 0\n6 4 1462 0 70 1462 0 1\n7 5 1433 0 0 1433 0 0\n\n\nNow some of our patients have two rows in the dataset corresponding to intervals where they have a different value of our new variable, agvhd. For example, Patient 1 now has two rows with a agvhd value of zero from time 0 to time 67, and a value of 1 from time 67 to time 2081.\n\n\n\nCox regression with time-dependent covariates\nNow that we’ve reshaped our data and added the new time-dependent aghvd variable, let’s fit a simple single variable cox regression model. We can use the same coxph() function as before, we just need to change our Surv() function to specify both the start and stop time for each interval using the time1 = and time2 = arguments.\n\nbmt_td_model = coxph(\n Surv(time = tstart, time2 = tstop, event = death) ~ agvhd, \n data = td_dat\n )\n\nsummary(bmt_td_model)\n\nCall:\ncoxph(formula = Surv(time = tstart, time2 = tstop, event = death) ~ \n agvhd, data = td_dat)\n\n n= 163, number of events= 80 \n\n coef exp(coef) se(coef) z Pr(>|z|)\nagvhd 0.3351 1.3980 0.2815 1.19 0.234\n\n exp(coef) exp(-coef) lower .95 upper .95\nagvhd 1.398 0.7153 0.8052 2.427\n\nConcordance= 0.535 (se = 0.024 )\nLikelihood ratio test= 1.33 on 1 df, p=0.2\nWald test = 1.42 on 1 df, p=0.2\nScore (logrank) test = 1.43 on 1 df, p=0.2\n\n\nAgain, we’ll visualize our cox model results using the ggforest() function from the survminer package.:\n\nggforest(bmt_td_model, data = td_dat)\n\n\n\n\n\n\n\n\nAs you can see from the forest plot, confidence interval, and p-value, there does not appear to be a strong association between death and acute graft-versus-host disease in the context of our simple model.", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2408,7 +2408,7 @@ "href": "new_pages/survival_analysis.html#resources", "title": "27  Survival analysis", "section": "27.7 Resources", - "text": "27.7 Resources\nSurvival Analysis Part I: Basic concepts and first analyses\nSurvival Analysis in R\nSurvival analysis in infectious disease research: Describing events in time\nChapter on advanced survival models Princeton\nUsing Time Dependent Covariates and Time Dependent Coefficients in the Cox Model\nSurvival analysis cheatsheet R\nSurvminer cheatsheet\nPaper on different survival measures for cancer registry data with Rcode provided as supplementary materials", + "text": "27.7 Resources\nSurvival Analysis Part I: Basic concepts and first analyses\nSurvival Analysis in R\nSurvival analysis in infectious disease research: Describing events in time\nUsing Time Dependent Covariates and Time Dependent Coefficients in the Cox Model\nSurvival analysis cheatsheet R\nSurvminer cheatsheet\nPaper on different survival measures for cancer registry data with Rcode provided as supplementary materials", "crumbs": [ "Analysis", "27  Survival analysis" @@ -2441,7 +2441,7 @@ "href": "new_pages/gis.html#key-terms", "title": "28  GIS basics", "section": "28.2 Key terms", - "text": "28.2 Key terms\nBelow we introduce some key terminology. For a thorough introduction to GIS and spatial analysis, we suggest that you review one of the longer tutorials or courses listed in the References section.\nGeographic Information System (GIS) - A GIS is a framework or environment for gathering, managing, analyzing, and visualizing spatial data.\n\nGIS software\nSome popular GIS software allow point-and-click interaction for map development and spatial analysis. These tools comes with advantages such as not needing to learn code and the ease of manually selecting and placing icons and features on a map. Here are two popular ones:\nArcGIS - A commercial GIS software developed by the company ESRI, which is very popular but quite expensive\nQGIS - A free open-source GIS software that can do almost anything that ArcGIS can do. You can download QGIS here\nUsing R as a GIS can seem more intimidating at first because instead of “point-and-click”, it has a “command-line interface” (you must code to acquire the desired outcome). However, this is a major advantage if you need to repetitively produce maps or create an analysis that is reproducible.\n\n\nSpatial data\nThe two primary forms of spatial data used in GIS are vector and raster data:\nVector Data - The most common format of spatial data used in GIS, vector data are comprised of geometric features of vertices and paths. Vector spatial data can be further divided into three widely-used types:\n\nPoints - A point consists of a coordinate pair (x,y) representing a specific location in a coordinate system. Points are the most basic form of spatial data, and may be used to denote a case (i.e. patient home) or a location (i.e. hospital) on a map.\nLines - A line is composed of two connected points. Lines have a length, and may be used to denote things like roads or rivers.\nPolygons - A polygon is composed of at least three line segments connected by points. Polygon features have a length (i.e. the perimeter of the area) as well as an area measurement. Polygons may be used to note an area (i.e. a village) or a structure (i.e. the actual area of a hospital).\n\nRaster Data - An alternative format for spatial data, raster data is a matrix of cells (e.g. pixels) with each cell containing information such as height, temperature, slope, forest cover, etc. These are often aerial photographs, satellite imagery, etc. Rasters can also be used as “base maps” below vector data.\n\n\nVisualizing spatial data\nTo visually represent spatial data on a map, GIS software requires you to provide sufficient information about where different features should be, in relation to one another. If you are using vector data, which will be true for most use cases, this information will typically be stored in a shapefile:\nShapefiles - A shapefile is a common data format for storing “vector” spatial data consisting or lines, points, or polygons. A single shapefile is actually a collection of at least three files - .shp, .shx, and .dbf. All of these sub-component files must be present in a given directory (folder) for the shapefile to be readable. These associated files can be compressed into a ZIP folder to be sent via email or download from a website.\nThe shapefile will contain information about the features themselves, as well as where to locate them on the Earth’s surface. This is important because while the Earth is a globe, maps are typically two-dimensional; choices about how to “flatten” spatial data can have a big impact on the look and interpretation of the resulting map.\nCoordinate Reference Systems (CRS) - A CRS is a coordinate-based system used to locate geographical features on the Earth’s surface. It has a few key components:\n\nCoordinate System - There are many many different coordinate systems, so make sure you know which system your coordinates are from. Degrees of latitude/longitude are common, but you could also see UTM coordinates.\nUnits - Know what the units are for your coordinate system (e.g. decimal degrees, meters)\nDatum - A particular modeled version of the Earth. These have been revised over the years, so ensure that your map layers are using the same datum.\nProjection - A reference to the mathematical equation that was used to project the truly round earth onto a flat surface (map).\n\nRemember that you can summarise spatial data without using the mapping tools shown below. Sometimes a simple table by geography (e.g. district, country, etc.) is all that is needed!", + "text": "28.2 Key terms\nBelow we introduce some key terminology. For a thorough introduction to GIS and spatial analysis, we suggest that you review one of the longer tutorials or courses listed in the References section.\nGeographic Information System (GIS) - A GIS is a framework or environment for gathering, managing, analyzing, and visualizing spatial data.\n\nGIS software\nSome popular GIS software allow point-and-click interaction for map development and spatial analysis. These tools comes with advantages such as not needing to learn code and the ease of manually selecting and placing icons and features on a map. Here are two popular ones:\nArcGIS - A commercial GIS software developed by the company ESRI, which is very popular but quite expensive.\nQGIS - A free open-source GIS software that can do almost anything that ArcGIS can do. You can download QGIS here.\nUsing R as a GIS can seem more intimidating at first because instead of “point-and-click”, it has a “command-line interface” (you must code to acquire the desired outcome). However, this is a major advantage if you need to repetitively produce maps or create an analysis that is reproducible.\n\n\nSpatial data\nThe two primary forms of spatial data used in GIS are vector and raster data.\nVector Data - The most common format of spatial data used in GIS, vector data are comprised of geometric features of vertices and paths. Vector spatial data can be further divided into three widely-used types:\n\nPoints - A point consists of a coordinate pair (x,y) representing a specific location in a coordinate system. Points are the most basic form of spatial data, and may be used to denote a case (i.e. patient home) or a location (i.e. hospital) on a map.\nLines - A line is composed of two connected points. Lines have a length, and may be used to denote things like roads or rivers.\nPolygons - A polygon is composed of at least three line segments connected by points. Polygon features have a length (i.e. the perimeter of the area) as well as an area measurement. Polygons may be used to note an area (i.e. a village) or a structure (i.e. the actual area of a hospital).\n\nRaster Data - An alternative format for spatial data, raster data is a matrix of cells (e.g. pixels) with each cell containing information such as height, temperature, slope, forest cover, etc. These are often aerial photographs, satellite imagery, etc. Rasters can also be used as “base maps” below vector data.\n\n\nVisualizing spatial data\nTo visually represent spatial data on a map, GIS software requires you to provide sufficient information about where different features should be, in relation to one another. If you are using vector data, which will be true for most use cases, this information will typically be stored in a shapefile:\nShapefiles - A shapefile is a common data format for storing “vector” spatial data consisting or lines, points, or polygons. A single shapefile is actually a collection of at least three files - .shp, .shx, and .dbf. All of these sub-component files must be present in a given directory (folder) for the shapefile to be readable. These associated files can be compressed into a ZIP folder to be sent via email or download from a website.\nThe shapefile will contain information about the features themselves, as well as where to locate them on the Earth’s surface. This is important because while the Earth is a globe, maps are typically two-dimensional; choices about how to “flatten” spatial data can have a big impact on the look and interpretation of the resulting map.\nCoordinate Reference Systems (CRS) - A CRS is a coordinate-based system used to locate geographical features on the Earth’s surface. It has a few key components:\n\nCoordinate System - There are many many different coordinate systems, so make sure you know which system your coordinates are from. Degrees of latitude/longitude are common, but you could also see UTM coordinates.\nUnits - Know what the units are for your coordinate system (e.g. decimal degrees, meters).\nDatum - A particular modeled version of the Earth. These have been revised over the years, so ensure that your map layers are using the same datum.\nProjection - A reference to the mathematical equation that was used to project the truly round earth onto a flat surface (map).\n\nRemember that you can summarise spatial data without using the mapping tools shown below. Sometimes a simple table by geography (e.g. district, country, etc.) is all that is needed!", "crumbs": [ "Analysis", "28  GIS basics" @@ -2452,7 +2452,7 @@ "href": "new_pages/gis.html#getting-started-with-gis", "title": "28  GIS basics", "section": "28.3 Getting started with GIS", - "text": "28.3 Getting started with GIS\nThere are a couple of key items you will need to have and to think about to make a map. These include:\n\nA dataset – this can be in a spatial data format (such as shapefiles, as noted above) or it may not be in a spatial format (for instance just as a csv).\nIf your dataset is not in a spatial format you will also need a reference dataset. Reference data consists of the spatial representation of the data and the related attributes, which would include material containing the location and address information of specific features.\n\nIf you are working with pre-defined geographic boundaries (for example, administrative regions), reference shapefiles are often freely available to download from a government agency or data sharing organization. When in doubt, a good place to start is to Google “[regions] shapefile”\nIf you have address information, but no latitude and longitude, you may need to use a geocoding engine to get the spatial reference data for your records.\n\nAn idea about how you want to present the information in your datasets to your target audience. There are many different types of maps, and it is important to think about which type of map best fits your needs.\n\n\nTypes of maps for visualizing your data\nChoropleth map - a type of thematic map where colors, shading, or patterns are used to represent geographic regions in relation to their value of an attribute. For instance a larger value could be indicated by a darker colour than a smaller value. This type of map is particularly useful when visualizing a variable and how it changes across defined regions or geopolitical areas.\n\n\n\n\n\n\n\n\n\nCase density heatmap - a type of thematic map where colours are used to represent intensity of a value, however, it does not use defined regions or geopolitical boundaries to group data. This type of map is typically used for showing ‘hot spots’ or areas with a high density or concentration of points.\n\n\n\n\n\n\n\n\n\nDot density map - a thematic map type that uses dots to represent attribute values in your data. This type of map is best used to visualize the scatter of your data and visually scan for clusters.\nProportional symbols map (graduated symbols map) - a thematic map similar to a choropleth map, but instead of using colour to indicate the value of an attribute it uses a symbol (usually a circle) in relation to the value. For instance a larger value could be indicated by a larger symbol than a smaller value. This type of map is best used when you want to visualize the size or quantity of your data across geographic regions.\nYou can also combine several different types of visualizations to show complex geographic patterns. For example, the cases (dots) in the map below are colored according to their closest health facility (see legend). The large red circles show health facility catchment areas of a certain radius, and the bright red case-dots those that were outside any catchment range:\n\n\n\n\n\n\n\n\n\nNote: The primary focus of this GIS page is based on the context of field outbreak response. Therefore the contents of the page will cover the basic spatial data manipulations, visualizations, and analyses.", + "text": "28.3 Getting started with GIS\nThere are a couple of key items you will need to have and to think about to make a map. These include:\n\nA dataset – this can be in a spatial data format (such as shapefiles, as noted above) or it may not be in a spatial format (for instance just as a csv).\nIf your dataset is not in a spatial format you will also need a reference dataset. Reference data consists of the spatial representation of the data and the related attributes, which would include material containing the location and address information of specific features.\n\nIf you are working with pre-defined geographic boundaries (for example, administrative regions), reference shapefiles are often freely available to download from a government agency or data sharing organization. When in doubt, a good place to start is to Google “[regions] shapefile”.\nIf you have address information, but no latitude and longitude, you may need to use a geocoding engine to get the spatial reference data for your records.\n\nAn idea about how you want to present the information in your datasets to your target audience. There are many different types of maps, and it is important to think about which type of map best fits your needs.\n\n\nTypes of maps for visualizing your data\nChoropleth map - a type of thematic map where colors, shading, or patterns are used to represent geographic regions in relation to their value of an attribute. For instance a larger value could be indicated by a darker colour than a smaller value. This type of map is particularly useful when visualizing a variable and how it changes across defined regions or geopolitical areas.\n\n\n\n\n\n\n\n\n\nCase density heatmap - a type of thematic map where colours are used to represent intensity of a value, however, it does not use defined regions or geopolitical boundaries to group data. This type of map is typically used for showing ‘hot spots’ or areas with a high density or concentration of points.\n\n\n\n\n\n\n\n\n\nDot density map - a thematic map type that uses dots to represent attribute values in your data. This type of map is best used to visualize the scatter of your data and visually scan for clusters.\nProportional symbols map (graduated symbols map) - a thematic map similar to a choropleth map, but instead of using colour to indicate the value of an attribute it uses a symbol (usually a circle) in relation to the value. For instance a larger value could be indicated by a larger symbol than a smaller value. This type of map is best used when you want to visualize the size or quantity of your data across geographic regions.\nYou can also combine several different types of visualizations to show complex geographic patterns. For example, the cases (dots) in the map below are colored according to their closest health facility (see legend). The large black circles show health facility catchment areas of a certain radius, and the bright red case-dots those that were outside any catchment range:\n\n\n\n\n\n\n\n\n\nNote: The primary focus of this GIS page is based on the context of field outbreak response. Therefore the contents of the page will cover the basic spatial data manipulations, visualizations, and analyses.", "crumbs": [ "Analysis", "28  GIS basics" @@ -2463,7 +2463,7 @@ "href": "new_pages/gis.html#preparation", "title": "28  GIS basics", "section": "28.4 Preparation", - "text": "28.4 Preparation\n\nLoad packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # to import data\n here, # to locate files\n tidyverse, # to clean, handle, and plot the data (includes ggplot2 package)\n sf, # to manage spatial data using a Simple Feature format\n tmap, # to produce simple maps, works for both interactive and static maps\n janitor, # to clean column names\n OpenStreetMap, # to add OSM basemap in ggplot map\n spdep # spatial statistics\n )\n\nYou can see an overview of all the R packages that deal with spatial data at the CRAN “Spatial Task View”.\n\n\nSample case data\nFor demonstration purposes, we will work with a random sample of 1000 cases from the simulated Ebola epidemic linelist dataframe (computationally, working with fewer cases is easier to display in this handbook). If you want to follow along, click to download the “clean” linelist (as .rds file).\nSince we are taking a random sample of the cases, your results may look slightly different from what is demonstrated here when you run the codes on your own.\nImport data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import clean case linelist\nlinelist <- import(\"linelist_cleaned.rds\") \n\nNext we select a random sample of 1000 rows using sample() from base R.\n\n# generate 1000 random row numbers, from the number of rows in linelist\nsample_rows <- sample(nrow(linelist), 1000)\n\n# subset linelist to keep only the sample rows, and all columns\nlinelist <- linelist[sample_rows,]\n\nNow we want to convert this linelist which is class dataframe, to an object of class “sf” (spatial features). Given that the linelist has two columns “lon” and “lat” representing the longitude and latitude of each case’s residence, this will be easy.\nWe use the package sf (spatial features) and its function st_as_sf() to create the new object we call linelist_sf. This new object looks essentially the same as the linelist, but the columns lon and lat have been designated as coordinate columns, and a coordinate reference system (CRS) has been assigned for when the points are displayed. 4326 identifies our coordinates as based on the World Geodetic System 1984 (WGS84) - which is standard for GPS coordinates.\n\n# Create sf object\nlinelist_sf <- linelist %>%\n sf::st_as_sf(coords = c(\"lon\", \"lat\"), crs = 4326)\n\nThis is how the original linelist dataframe looks like. In this demonstration, we will only use the column date_onset and geometry (which was constructed from the longitude and latitude fields above and is the last column in the data frame).\n\nDT::datatable(head(linelist_sf, 10), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' )\n\n\n\n\n\n\n\nAdmin boundary shapefiles\nSierra Leone: Admin boundary shapefiles\nIn advance, we have downloaded all administrative boundaries for Sierra Leone from the Humanitarian Data Exchange (HDX) website here. Alternatively, you can download these and all other example data for this handbook via our R package, as explained in the Download handbook and data page.\nNow we are going to do the following to save the Admin Level 3 shapefile in R:\n\nImport the shapefile\n\nClean the column names\n\nFilter rows to keep only areas of interest\n\nTo import a shapefile we use the read_sf() function from sf. It is provided the filepath via here(). - in our case the file is within our R project in the “data”, “gis”, and “shp” subfolders, with filename “sle_adm3.shp” (see pages on Import and export and R projects for more information). You will need to provide your own file path.\nNext we use clean_names() from the janitor package to standardize the column names of the shapefile. We also use filter() to keep only the rows with admin2name of “Western Area Urban” or “Western Area Rural”.\n\n# ADM3 level clean\nsle_adm3 <- sle_adm3_raw %>%\n janitor::clean_names() %>% # standardize column names\n filter(admin2name %in% c(\"Western Area Urban\", \"Western Area Rural\")) # filter to keep certain areas\n\nBelow you can see the how the shapefile looks after import and cleaning. Scroll to the right to see how there are columns with admin level 0 (country), admin level 1, admin level 2, and finally admin level 3. Each level has a character name and a unique identifier “pcode”. The pcode expands with each increasing admin level e.g. SL (Sierra Leone) -> SL04 (Western) -> SL0410 (Western Area Rural) -> SL040101 (Koya Rural).\n\n\n\n\n\n\n\n\nPopulation data\nSierra Leone: Population by ADM3\nThese data can again be downloaded from HDX (link here) or via our epirhandbook R package as explained in this page. We use import() to load the .csv file. We also pass the imported file to clean_names() to standardize the column name syntax.\n\n# Population by ADM3\nsle_adm3_pop <- import(here(\"data\", \"gis\", \"population\", \"sle_admpop_adm3_2020.csv\")) %>%\n janitor::clean_names()\n\nHere is what the population file looks like. Scroll to the right to see how each jurisdiction has columns with male population, female populaton, total population, and the population break-down in columns by age group.\n\n\n\n\n\n\n\n\nHealth Facilities\nSierra Leone: Health facility data from OpenStreetMap\nAgain we have downloaded the locations of health facilities from HDX here or via instructions in the Download handbook and data page.\nWe import the facility points shapefile with read_sf(), again clean the column names, and then filter to keep only the points tagged as either “hospital”, “clinic”, or “doctors”.\n\n# OSM health facility shapefile\nsle_hf <- sf::read_sf(here(\"data\", \"gis\", \"shp\", \"sle_hf.shp\")) %>% \n janitor::clean_names() %>%\n filter(amenity %in% c(\"hospital\", \"clinic\", \"doctors\"))\n\nHere is the resulting dataframe - scroll right to see the facility name and geometry coordinates.", + "text": "28.4 Preparation\n\nLoad packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # to import data\n here, # to locate files\n tidyverse, # to clean, handle, and plot the data (includes ggplot2 package)\n sf, # to manage spatial data using a Simple Feature format\n tmap, # to produce simple maps, works for both interactive and static maps\n janitor, # to clean column names\n OpenStreetMap, # to add OSM basemap in ggplot map\n maptiles, # for creating basemaps\n tidyterra, # for plotting basemaps\n spdep # spatial statistics\n )\n\nYou can see an overview of all the R packages that deal with spatial data at the CRAN “Spatial Task View”.\n\n\nSample case data\nFor demonstration purposes, we will work with a random sample of 1000 cases from the simulated Ebola epidemic linelist dataframe (computationally, working with fewer cases is easier to display in this handbook). If you want to follow along, click to download the “clean” linelist (as .rds file).\nSince we are taking a random sample of the cases, your results may look slightly different from what is demonstrated here when you run the codes on your own.\nImport data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import clean case linelist\nlinelist <- import(\"linelist_cleaned.rds\") \n\nNext we select a random sample of 1000 rows using sample() from base R.\n\n# generate 1000 random row numbers, from the number of rows in linelist\nsample_rows <- sample(nrow(linelist), 1000)\n\n# subset linelist to keep only the sample rows, and all columns\nlinelist <- linelist[sample_rows,]\n\nNow we want to convert this linelist which is class dataframe, to an object of class “sf” (spatial features). Given that the linelist has two columns “lon” and “lat” representing the longitude and latitude of each case’s residence, this will be easy.\nWe use the package sf (spatial features) and its function st_as_sf() to create the new object we call linelist_sf. This new object looks essentially the same as the linelist, but the columns lon and lat have been designated as coordinate columns, and a coordinate reference system (CRS) has been assigned for when the points are displayed. 4326 identifies our coordinates as based on the World Geodetic System 1984 (WGS84) - which is standard for GPS coordinates.\n\n# Create sf object\nlinelist_sf <- linelist %>%\n sf::st_as_sf(coords = c(\"lon\", \"lat\"), crs = 4326)\n\nThis is how the original linelist dataframe looks like. In this demonstration, we will only use the column date_onset and geometry (which was constructed from the longitude and latitude fields above and is the last column in the data frame).\n\nDT::datatable(head(linelist_sf, 10), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' )\n\n\n\n\n\n\n\nAdmin boundary shapefiles\nSierra Leone: Admin boundary shapefiles\nIn advance, we have downloaded all administrative boundaries for Sierra Leone from the Humanitarian Data Exchange (HDX) website here. Alternatively, you can download these and all other example data for this handbook via our R package, as explained in the Download handbook and data page.\nNow we are going to do the following to save the Admin Level 3 shapefile in R:\n\nImport the shapefile\nClean the column names\nFilter rows to keep only areas of interest\n\nTo import a shapefile we use the read_sf() function from sf. It is provided the filepath via here(). - in our case the file is within our R project in the “data”, “gis”, and “shp” subfolders, with filename “sle_adm3.shp” (see pages on Import and export and R projects for more information). You will need to provide your own file path.\nNext we use clean_names() from the janitor package to standardize the column names of the shapefile. We also use filter() to keep only the rows with admin2name of “Western Area Urban” or “Western Area Rural”.\n\n# ADM3 level clean\nsle_adm3 <- sle_adm3_raw %>%\n janitor::clean_names() %>% # standardize column names\n filter(admin2name %in% c(\"Western Area Urban\", \"Western Area Rural\")) # filter to keep certain areas\n\nBelow you can see the how the shapefile looks after import and cleaning. Scroll to the right to see how there are columns with admin level 0 (country), admin level 1, admin level 2, and finally admin level 3. Each level has a character name and a unique identifier “pcode”. The pcode expands with each increasing admin level e.g. SL (Sierra Leone) -> SL04 (Western) -> SL0410 (Western Area Rural) -> SL040101 (Koya Rural).\n\n\n\n\n\n\n\n\nPopulation data\nSierra Leone: Population by ADM3\nThese data can again be downloaded from HDX (link here) or via our epirhandbook R package as explained in this page. We use import() to load the .csv file. We also pass the imported file to clean_names() to standardize the column name syntax.\n\n# Population by ADM3\nsle_adm3_pop <- import(here(\"data\", \"gis\", \"population\", \"sle_admpop_adm3_2020.csv\")) %>%\n janitor::clean_names()\n\nHere is what the population file looks like. Scroll to the right to see how each jurisdiction has columns with male population, female populaton, total population, and the population break-down in columns by age group.\n\n\n\n\n\n\n\n\nHealth Facilities\nSierra Leone: Health facility data from OpenStreetMap\nAgain we have downloaded the locations of health facilities from HDX here or via instructions in the Download handbook and data page.\nWe import the facility points shapefile with read_sf(), again clean the column names, and then filter to keep only the points tagged as either “hospital”, “clinic”, or “doctors”.\n\n# OSM health facility shapefile\nsle_hf <- sf::read_sf(here(\"data\", \"gis\", \"shp\", \"sle_hf.shp\")) %>% \n janitor::clean_names() %>%\n filter(amenity %in% c(\"hospital\", \"clinic\", \"doctors\"))\n\nHere is the resulting dataframe - scroll right to see the facility name and geometry coordinates.", "crumbs": [ "Analysis", "28  GIS basics" @@ -2474,7 +2474,7 @@ "href": "new_pages/gis.html#plotting-coordinates", "title": "28  GIS basics", "section": "28.5 Plotting coordinates", - "text": "28.5 Plotting coordinates\nThe easiest way to plot X-Y coordinates (longitude/latitude, points), in this case of cases, is to draw them as points directly from the linelist_sf object which we created in the preparation section.\nThe package tmap offers simple mapping capabilities for both static (“plot” mode) and interactive (“view” mode) with just a few lines of code. The tmap syntax is similar to that of ggplot2, such that commands are added to each other with +. Read more detail in this vignette.\n\nSet the tmap mode. In this case we will use “plot” mode, which produces static outputs.\n\n\ntmap_mode(\"plot\") # choose either \"view\" or \"plot\"\n\nBelow, the points are plotted alone.tm_shape() is provided with the linelist_sf objects. We then add points via tm_dots(), specifying the size and color. Because linelist_sf is an sf object, we have already designated the two columns that contain the lat/long coordinates and the coordinate reference system (CRS):\n\n# Just the cases (points)\ntm_shape(linelist_sf) + tm_dots(size=0.08, col='blue')\n\n\n\n\n\n\n\n\nAlone, the points do not tell us much. So we should also map the administrative boundaries:\nAgain we use tm_shape() (see documentation) but instead of providing the case points shapefile, we provide the administrative boundary shapefile (polygons).\nWith the bbox = argument (bbox stands for “bounding box”) we can specify the coordinate boundaries. First we show the map display without bbox, and then with it.\n\n# Just the administrative boundaries (polygons)\ntm_shape(sle_adm3) + # admin boundaries shapefile\n tm_polygons(col = \"#F7F7F7\")+ # show polygons in light grey\n tm_borders(col = \"#000000\", # show borders with color and line weight\n lwd = 2) +\n tm_text(\"admin3name\") # column text to display for each polygon\n\n\n# Same as above, but with zoom from bounding box\ntm_shape(sle_adm3,\n bbox = c(-13.3, 8.43, # corner\n -13.2, 8.5)) + # corner\n tm_polygons(col = \"#F7F7F7\") +\n tm_borders(col = \"#000000\", lwd = 2) +\n tm_text(\"admin3name\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAnd now both points and polygons together:\n\n# All together\ntm_shape(sle_adm3, bbox = c(-13.3, 8.43, -13.2, 8.5)) + #\n tm_polygons(col = \"#F7F7F7\") +\n tm_borders(col = \"#000000\", lwd = 2) +\n tm_text(\"admin3name\")+\ntm_shape(linelist_sf) +\n tm_dots(size=0.08, col='blue', alpha = 0.5) +\n tm_layout(title = \"Distribution of Ebola cases\") # give title to map\n\n\n\n\n\n\n\n\nTo read a good comparison of mapping options in R, see this blog post.", + "text": "28.5 Plotting coordinates\nThe easiest way to plot X-Y coordinates (longitude/latitude, points), in this case of cases, is to draw them as points directly from the linelist_sf object which we created in the preparation section.\nThe package tmap offers simple mapping capabilities for both static (“plot” mode) and interactive (“view” mode) with just a few lines of code. The tmap syntax is similar to that of ggplot2, such that commands are added to each other with +. Read more detail in this vignette.\n\nSet the tmap mode. In this case we will use “plot” mode, which produces static outputs.\n\n\ntmap_mode(\"plot\") # choose either \"view\" or \"plot\"\n\nBelow, the points are plotted alone. The function tm_shape() is provided with the linelist_sf objects. We then add points via tm_dots(), specifying the size and color. Because linelist_sf is an sf object, we have already designated the two columns that contain the lat/long coordinates and the coordinate reference system (CRS):\n\n# Just the cases (points)\ntm_shape(linelist_sf) + \n tm_dots(size = 0.08, col='blue')\n\n\n\n\n\n\n\n\nAlone, the points do not tell us much. So we should also map the administrative boundaries:\nAgain we use tm_shape() (see documentation) but instead of providing the case points shapefile, we provide the administrative boundary shapefile (polygons).\nWith the bbox = argument (bbox stands for “bounding box”) we can specify the coordinate boundaries. First we show the map display without bbox, and then with it.\n\n# Just the administrative boundaries (polygons)\ntm_shape(sle_adm3) + # admin boundaries shapefile\n tm_polygons(col = \"#F7F7F7\") + # show polygons in light grey\n tm_borders(col = \"#000000\", # show borders with color and line weight\n lwd = 2) +\n tm_text(\"admin3name\") # column text to display for each polygon\n\n\n# Same as above, but with zoom from bounding box\ntm_shape(sle_adm3,\n bbox = c(-13.3, 8.43, # corner\n -13.2, 8.5)) + # corner\n tm_polygons(col = \"#F7F7F7\") +\n tm_borders(col = \"#000000\", lwd = 2) +\n tm_text(\"admin3name\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAnd now both points and polygons together:\n\n# All together\ntm_shape(sle_adm3, bbox = c(-13.3, 8.43, -13.2, 8.5)) + #\n tm_polygons(col = \"#F7F7F7\") +\n tm_borders(col = \"#000000\", lwd = 2) +\n tm_text(\"admin3name\") +\ntm_shape(linelist_sf) +\n tm_dots(size = 0.08, col = 'blue', alpha = 0.5) +\n tm_layout(title = \"Distribution of Ebola cases\") # give title to map\n\n\n\n\n\n\n\n\nTo read a good comparison of mapping options in R, see this blog post.", "crumbs": [ "Analysis", "28  GIS basics" @@ -2485,7 +2485,7 @@ "href": "new_pages/gis.html#spatial-joins", "title": "28  GIS basics", "section": "28.6 Spatial joins", - "text": "28.6 Spatial joins\nYou may be familiar with joining data from one dataset to another one. Several methods are discussed in the Joining data page of this handbook. A spatial join serves a similar purpose but leverages spatial relationships. Instead of relying on common values in columns to correctly match observations, you can utilize their spatial relationships, such as one feature being within another, or the nearest neighbor to another, or within a buffer of a certain radius from another, etc.\nThe sf package offers various methods for spatial joins. See more documentation about the st_join method and spatial join types in this reference.\n\nPoints in polygon\nSpatial assign administrative units to cases\nHere is an interesting conundrum: the case linelist does not contain any information about the administrative units of the cases. Although it is ideal to collect such information during the initial data collection phase, we can also assign administrative units to individual cases based on their spatial relationships (i.e. point intersects with a polygon).\nBelow, we will spatially intersect our case locations (points) with the ADM3 boundaries (polygons):\n\nBegin with the linelist (points)\n\nSpatial join to the boundaries, setting the type of join at “st_intersects”\n\nUse select() to keep only certain of the new administrative boundary columns\n\n\nlinelist_adm <- linelist_sf %>%\n \n # join the administrative boundary file to the linelist, based on spatial intersection\n sf::st_join(sle_adm3, join = st_intersects)\n\nAll the columns from sle_adms have been added to the linelist! Each case now has columns detailing the administrative levels that it falls within. In this example, we only want to keep two of the new columns (admin level 3), so we select() the old column names and just the two additional of interest:\n\nlinelist_adm <- linelist_sf %>%\n \n # join the administrative boundary file to the linelist, based on spatial intersection\n sf::st_join(sle_adm3, join = st_intersects) %>% \n \n # Keep the old column names and two new admin ones of interest\n select(names(linelist_sf), admin3name, admin3pcod)\n\nBelow, just for display purposes you can see the first ten cases and that their admin level 3 (ADM3) jurisdictions that have been attached, based on where the point spatially intersected with the polygon shapes.\n\n# Now you will see the ADM3 names attached to each case\nlinelist_adm %>% select(case_id, admin3name, admin3pcod)\n\nSimple feature collection with 1000 features and 3 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -13.27122 ymin: 8.448447 xmax: -13.20684 ymax: 8.490397\nGeodetic CRS: WGS 84\nFirst 10 features:\n case_id admin3name admin3pcod geometry\n376 cbce06 West II SL040207 POINT (-13.23143 8.466892)\n4115 ba5769 Mountain Rural SL040102 POINT (-13.21752 8.451579)\n5685 58982e West II SL040207 POINT (-13.25187 8.4793)\n5426 b4bc41 East II SL040204 POINT (-13.22543 8.486554)\n4951 95c347 Mountain Rural SL040102 POINT (-13.24198 8.454378)\n4252 4669b3 West III SL040208 POINT (-13.26526 8.471718)\n3728 135bec West III SL040208 POINT (-13.26403 8.475445)\n1852 fd9c82 West III SL040208 POINT (-13.26097 8.457779)\n4322 7525ac East II SL040204 POINT (-13.22541 8.485722)\n5227 5c033b East II SL040204 POINT (-13.21212 8.482889)\n\n\nNow we can describe our cases by administrative unit - something we were not able to do before the spatial join!\n\n# Make new dataframe containing counts of cases by administrative unit\ncase_adm3 <- linelist_adm %>% # begin with linelist with new admin cols\n as_tibble() %>% # convert to tibble for better display\n group_by(admin3pcod, admin3name) %>% # group by admin unit, both by name and pcode \n summarise(cases = n()) %>% # summarize and count rows\n arrange(desc(cases)) # arrange in descending order\n\ncase_adm3\n\n# A tibble: 10 × 3\n# Groups: admin3pcod [10]\n admin3pcod admin3name cases\n <chr> <chr> <int>\n 1 SL040102 Mountain Rural 252\n 2 SL040208 West III 247\n 3 SL040207 West II 182\n 4 SL040204 East II 120\n 5 SL040201 Central I 66\n 6 SL040206 West I 44\n 7 SL040203 East I 43\n 8 SL040205 East III 23\n 9 SL040202 Central II 22\n10 <NA> <NA> 1\n\n\nWe can also create a bar plot of case counts by administrative unit.\nIn this example, we begin the ggplot() with the linelist_adm, so that we can apply factor functions like fct_infreq() which orders the bars by frequency (see page on Factors for tips).\n\nggplot(\n data = linelist_adm, # begin with linelist containing admin unit info\n mapping = aes(\n x = fct_rev(fct_infreq(admin3name))))+ # x-axis is admin units, ordered by frequency (reversed)\n geom_bar()+ # create bars, height is number of rows\n coord_flip()+ # flip X and Y axes for easier reading of adm units\n theme_classic()+ # simplify background\n labs( # titles and labels\n x = \"Admin level 3\",\n y = \"Number of cases\",\n title = \"Number of cases, by adminstative unit\",\n caption = \"As determined by a spatial join, from 1000 randomly sampled cases from linelist\"\n )\n\n\n\n\n\n\n\n\n\n\n\nNearest neighbor\nFinding the nearest health facility / catchment area\nIt might be useful to know where the health facilities are located in relation to the disease hot spots.\nWe can use the st_nearest_feature join method from the st_join() function (sf package) to visualize the closest health facility to individual cases.\n\nWe begin with the shapefile linelist linelist_sf\n\nWe spatially join with sle_hf, which is the locations of health facilities and clinics (points)\n\n\n# Closest health facility to each case\nlinelist_sf_hf <- linelist_sf %>% # begin with linelist shapefile \n st_join(sle_hf, join = st_nearest_feature) %>% # data from nearest clinic joined to case data \n select(case_id, osm_id, name, amenity) %>% # keep columns of interest, including id, name, type, and geometry of healthcare facility\n rename(\"nearest_clinic\" = \"name\") # re-name for clarity\n\nWe can see below (first 50 rows) that the each case now has data on the nearest clinic/hospital\n\n\n\n\n\n\nWe can see that “Den Clinic” is the closest health facility for about ~30% of the cases.\n\n# Count cases by health facility\nhf_catchment <- linelist_sf_hf %>% # begin with linelist including nearest clinic data\n as.data.frame() %>% # convert from shapefile to dataframe\n count(nearest_clinic, # count rows by \"name\" (of clinic)\n name = \"case_n\") %>% # assign new counts column as \"case_n\"\n arrange(desc(case_n)) # arrange in descending order\n\nhf_catchment # print to console\n\n nearest_clinic case_n\n1 Den Clinic 386\n2 Shriners Hospitals for Children 336\n3 GINER HALL COMMUNITY HOSPITAL 151\n4 panasonic 51\n5 Princess Christian Maternity Hospital 32\n6 ARAB EGYPT CLINIC 20\n7 MABELL HEALTH CENTER 14\n8 <NA> 10\n\n\nTo visualize the results, we can use tmap - this time interactive mode for easier viewing\n\ntmap_mode(\"view\") # set tmap mode to interactive \n\n# plot the cases and clinic points \ntm_shape(linelist_sf_hf) + # plot cases\n tm_dots(size=0.08, # cases colored by nearest clinic\n col='nearest_clinic') + \ntm_shape(sle_hf) + # plot clinic facilities in large black dots\n tm_dots(size=0.3, col='black', alpha = 0.4) + \n tm_text(\"name\") + # overlay with name of facility\ntm_view(set.view = c(-13.2284, 8.4699, 13), # adjust zoom (center coords, zoom)\n set.zoom.limits = c(13,14))+\ntm_layout(title = \"Cases, colored by nearest clinic\")\n\n\n\n\n\n\n\nBuffers\nWe can also explore how many cases are located within 2.5km (~30 mins) walking distance from the closest health facility.\nNote: For more accurate distance calculations, it is better to re-project your sf object to the respective local map projection system such as UTM (Earth projected onto a planar surface). In this example, for simplicity we will stick to the World Geodetic System (WGS84) Geograhpic coordinate system (Earth represented in a spherical / round surface, therefore the units are in decimal degrees). We will use a general conversion of: 1 decimal degree = ~111km.\nSee more information about map projections and coordinate systems at this esri article. This blog talks about different types of map projection and how one can choose a suitable projection depending on the area of interest and the context of your map / analysis.\nFirst, create a circular buffer with a radius of ~2.5km around each health facility. This is done with the function st_buffer() from tmap. Because the unit of the map is in lat/long decimal degrees, that is how “0.02” is interpreted. If your map coordinate system is in meters, the number must be provided in meters.\n\nsle_hf_2k <- sle_hf %>%\n st_buffer(dist=0.02) # decimal degrees translating to approximately 2.5km \n\nBelow we plot the buffer zones themselves, with the :\n\ntmap_mode(\"plot\")\n# Create circular buffers\ntm_shape(sle_hf_2k) +\n tm_borders(col = \"black\", lwd = 2)+\ntm_shape(sle_hf) + # plot clinic facilities in large red dots\n tm_dots(size=0.3, col='black') \n\n\n\n\n\n\n\n\n**Second, we intersect these buffers with the cases (points) using st_join() and the join type of st_intersects*. That is, the data from the buffers are joined to the points that they intersect with.\n\n# Intersect the cases with the buffers\nlinelist_sf_hf_2k <- linelist_sf_hf %>%\n st_join(sle_hf_2k, join = st_intersects, left = TRUE) %>%\n filter(osm_id.x==osm_id.y | is.na(osm_id.y)) %>%\n select(case_id, osm_id.x, nearest_clinic, amenity.x, osm_id.y)\n\nNow we can count the results: nrow(linelist_sf_hf_2k[is.na(linelist_sf_hf_2k$osm_id.y),]) out of 1000 cases did not intersect with any buffer (that value is missing), and so live more than 30 mins walk from the nearest health facility.\n\n# Cases which did not get intersected with any of the health facility buffers\nlinelist_sf_hf_2k %>% \n filter(is.na(osm_id.y)) %>%\n nrow()\n\n[1] 1000\n\n\nWe can visualize the results such that cases that did not intersect with any buffer appear in red.\n\ntmap_mode(\"view\")\n\n# First display the cases in points\ntm_shape(linelist_sf_hf) +\n tm_dots(size=0.08, col='nearest_clinic') +\n\n# plot clinic facilities in large black dots\ntm_shape(sle_hf) + \n tm_dots(size=0.3, col='black')+ \n\n# Then overlay the health facility buffers in polylines\ntm_shape(sle_hf_2k) +\n tm_borders(col = \"black\", lwd = 2) +\n\n# Highlight cases that are not part of any health facility buffers\n# in red dots \ntm_shape(linelist_sf_hf_2k %>% filter(is.na(osm_id.y))) +\n tm_dots(size=0.1, col='red') +\ntm_view(set.view = c(-13.2284,8.4699, 13), set.zoom.limits = c(13,14))+\n\n# add title \ntm_layout(title = \"Cases by clinic catchment area\")\n\n\n\n\n\n\n\nOther spatial joins\nAlternative values for argument join include (from the documentation)\n\nst_contains_properly\n\nst_contains\n\nst_covered_by\n\nst_covers\n\nst_crosses\n\nst_disjoint\n\nst_equals_exact\n\nst_equals\n\nst_is_within_distance\n\nst_nearest_feature\n\nst_overlaps\n\nst_touches\n\nst_within", + "text": "28.6 Spatial joins\nYou may be familiar with joining data from one dataset to another one. Several methods are discussed in the Joining data page of this handbook. A spatial join serves a similar purpose but leverages spatial relationships. Instead of relying on common values in columns to correctly match observations, you can utilize their spatial relationships, such as one feature being within another, or the nearest neighbor to another, or within a buffer of a certain radius from another, etc.\nThe sf package offers various methods for spatial joins. See more documentation about the st_join() method and spatial join types in this reference.\n\nPoints in polygon\nSpatial assign administrative units to cases\nHere is an interesting conundrum: the case linelist does not contain any information about the administrative units of the cases. Although it is ideal to collect such information during the initial data collection phase, we can also assign administrative units to individual cases based on their spatial relationships (i.e. point intersects with a polygon).\nBelow, we will spatially intersect our case locations (points) with the ADM3 boundaries (polygons):\n\nBegin with the linelist (points)\nSpatial join to the boundaries, setting the type of join at “st_intersects”\nUse select() to keep only certain of the new administrative boundary columns\n\n\nlinelist_adm <- linelist_sf %>%\n # join the administrative boundary file to the linelist, based on spatial intersection\n sf::st_join(sle_adm3, join = st_intersects)\n\nAll the columns from sle_adms have been added to the linelist! Each case now has columns detailing the administrative levels that it falls within. In this example, we only want to keep two of the new columns (admin level 3), so we select() the old column names and just the two additional of interest:\n\nlinelist_adm <- linelist_sf %>%\n # join the administrative boundary file to the linelist, based on spatial intersection\n sf::st_join(sle_adm3, join = st_intersects) %>% \n # Keep the old column names and two new admin ones of interest\n select(names(linelist_sf), admin3name, admin3pcod)\n\nBelow, just for display purposes you can see the first ten cases and that their admin level 3 (ADM3) jurisdictions that have been attached, based on where the point spatially intersected with the polygon shapes.\n\n# Now you will see the ADM3 names attached to each case\nlinelist_adm %>% \n select(case_id, admin3name, admin3pcod)\n\nSimple feature collection with 1000 features and 3 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -13.27276 ymin: 8.448376 xmax: -13.20595 ymax: 8.490315\nGeodetic CRS: WGS 84\nFirst 10 features:\n case_id admin3name admin3pcod geometry\n4113 8911bd Central I SL040201 POINT (-13.23533 8.47648)\n3439 b02a58 East I SL040203 POINT (-13.21458 8.488009)\n2855 c70ead Mountain Rural SL040102 POINT (-13.22476 8.479803)\n5200 a1fc10 Mountain Rural SL040102 POINT (-13.2143 8.463362)\n1232 ba2431 West III SL040208 POINT (-13.26326 8.464279)\n4704 eeadcf Central II SL040202 POINT (-13.2387 8.478045)\n1654 c0bd90 Mountain Rural SL040102 POINT (-13.2213 8.462379)\n3894 ffae72 West II SL040207 POINT (-13.23586 8.469718)\n238 12a0e2 East II SL040204 POINT (-13.2252 8.485305)\n3773 1101ae East I SL040203 POINT (-13.21703 8.48873)\n\n\nNow we can describe our cases by administrative unit - something we were not able to do before the spatial join!\n\n# Make new dataframe containing counts of cases by administrative unit\ncase_adm3 <- linelist_adm %>% # begin with linelist with new admin cols\n as_tibble() %>% # convert to tibble for better display\n group_by(admin3pcod, admin3name) %>% # group by admin unit, both by name and pcode \n summarise(cases = n()) %>% # summarize and count rows\n arrange(desc(cases)) # arrange in descending order\n\ncase_adm3\n\n# A tibble: 10 × 3\n# Groups: admin3pcod [10]\n admin3pcod admin3name cases\n <chr> <chr> <int>\n 1 SL040102 Mountain Rural 282\n 2 SL040208 West III 213\n 3 SL040207 West II 184\n 4 SL040204 East II 122\n 5 SL040203 East I 60\n 6 SL040201 Central I 51\n 7 SL040206 West I 48\n 8 SL040202 Central II 18\n 9 SL040205 East III 17\n10 <NA> <NA> 5\n\n\nWe can also create a bar plot of case counts by administrative unit.\nIn this example, we begin the ggplot() with the linelist_adm, so that we can apply factor functions like fct_infreq() which orders the bars by frequency (see page on Factors for tips).\n\nggplot(\n data = linelist_adm, # begin with linelist containing admin unit info\n mapping = aes(\n x = fct_rev(fct_infreq(admin3name)))) + # x-axis is admin units, ordered by frequency (reversed)\n geom_bar() + # create bars, height is number of rows\n coord_flip() + # flip X and Y axes for easier reading of adm units\n theme_classic() + # simplify background\n labs( # titles and labels\n x = \"Admin level 3\",\n y = \"Number of cases\",\n title = \"Number of cases, by adminstative unit\",\n caption = \"As determined by a spatial join, from 1000 randomly sampled cases from linelist\"\n )\n\n\n\n\n\n\n\n\n\n\n\nNearest neighbor\nFinding the nearest health facility / catchment area\nIt might be useful to know where the health facilities are located in relation to the disease hot spots.\nWe can use the st_nearest_feature join method from the st_join() function (sf package) to visualize the closest health facility to individual cases.\n\nWe begin with the shapefile linelist linelist_sf\nWe spatially join with sle_hf, which is the locations of health facilities and clinics (points)\n\n\n# Closest health facility to each case\nlinelist_sf_hf <- linelist_sf %>% # begin with linelist shapefile \n st_join(sle_hf, join = st_nearest_feature) %>% # data from nearest clinic joined to case data \n select(case_id, osm_id, name, amenity) %>% # keep columns of interest, including id, name, type, and geometry of healthcare facility\n rename(\"nearest_clinic\" = \"name\") # re-name for clarity\n\nWe can see below (first 50 rows) that the each case now has data on the nearest clinic/hospital.\n\n\n\n\n\n\nWe can see that “Den Clinic” is the closest health facility for about 34.6% of the cases.\n\n# Count cases by health facility\nhf_catchment <- linelist_sf_hf %>% # begin with linelist including nearest clinic data\n as.data.frame() %>% # convert from shapefile to dataframe\n count(nearest_clinic, # count rows by \"name\" (of clinic)\n name = \"case_n\") %>% # assign new counts column as \"case_n\"\n arrange(desc(case_n)) %>% # arrange in descending order\n mutate(percent = case_n/sum(case_n)) #Calculate % of cases per nearest clinic\n \nhf_catchment # print to console\n\n nearest_clinic case_n percent\n1 Den Clinic 356 0.356\n2 Shriners Hospitals for Children 322 0.322\n3 GINER HALL COMMUNITY HOSPITAL 191 0.191\n4 panasonic 53 0.053\n5 Princess Christian Maternity Hospital 37 0.037\n6 <NA> 17 0.017\n7 ARAB EGYPT CLINIC 14 0.014\n8 MABELL HEALTH CENTER 10 0.010\n\n\nTo visualize the results, we can use tmap - this time interactive mode for easier viewing\n\ntmap_mode(\"view\") # set tmap mode to interactive \n\n# plot the cases and clinic points \ntm_shape(linelist_sf_hf) + # plot cases\n tm_dots(size=0.08, # cases colored by nearest clinic\n col='nearest_clinic') + \ntm_shape(sle_hf) + # plot clinic facilities in large black dots\n tm_dots(size=0.3, col='black', alpha = 0.4) + \n tm_text(\"name\") + # overlay with name of facility\ntm_view(set.view = c(-13.2284, 8.4699, 13), # adjust zoom (center coords, zoom)\n set.zoom.limits = c(13,14)) +\ntm_layout(title = \"Cases, colored by nearest clinic\")\n\n\n\n\n\n\n\nBuffers\nWe can also explore how many cases are located within 2.5km (~30 mins) walking distance from the closest health facility.\nNote: For more accurate distance calculations, it is better to re-project your sf object to the respective local map projection system such as UTM (Earth projected onto a planar surface). In this example, for simplicity we will stick to the World Geodetic System (WGS84) Geograhpic coordinate system (Earth represented in a spherical / round surface, therefore the units are in decimal degrees). We will use a general conversion of: 1 decimal degree = ~111km.\nSee more information about map projections and coordinate systems at this esri article. This blog talks about different types of map projection and how one can choose a suitable projection depending on the area of interest and the context of your map / analysis.\nFirst, create a circular buffer with a radius of ~2.5km around each health facility. This is done with the function st_buffer() from tmap. Because the unit of the map is in lat/long decimal degrees, that is how “0.02” is interpreted. If your map coordinate system is in meters, the number must be provided in meters.\n\nsle_hf_2k <- sle_hf %>%\n st_buffer(dist = 0.02) # decimal degrees translating to approximately 2.5km \n\nBelow we plot the buffer zones themselves, with the :\n\ntmap_mode(\"plot\")\n# Create circular buffers\ntm_shape(sle_hf_2k) +\n tm_borders(col = \"black\", lwd = 2) +\ntm_shape(sle_hf) + # plot clinic facilities in large red dots\n tm_dots(size = 0.3, col = 'black') \n\n\n\n\n\n\n\n\nSecond, we intersect these buffers with the cases (points) using st_join() and the join type of st_intersects. That is, the data from the buffers are joined to the points that they intersect with.\n\n# Intersect the cases with the buffers\nlinelist_sf_hf_2k <- linelist_sf_hf %>%\n st_join(sle_hf_2k, join = st_intersects, left = TRUE) %>%\n filter(osm_id.x == osm_id.y | is.na(osm_id.y)) %>%\n select(case_id, osm_id.x, nearest_clinic, amenity.x, osm_id.y)\n\nNow we can count the results: nrow(linelist_sf_hf_2k[is.na(linelist_sf_hf_2k$osm_id.y),]) out of 1000 cases did not intersect with any buffer (that value is missing), and so live more than 30 mins walk from the nearest health facility.\n\n# Cases which did not get intersected with any of the health facility buffers\nlinelist_sf_hf_2k %>% \n filter(is.na(osm_id.y)) %>%\n nrow()\n\n[1] 1000\n\n\nWe can visualize the results such that cases that did not intersect with any buffer appear in red.\n\ntmap_mode(\"view\")\n\n# First display the cases in points\ntm_shape(linelist_sf_hf) +\n tm_dots(size = 0.08, col = 'nearest_clinic') +\n\n# plot clinic facilities in large black dots\ntm_shape(sle_hf) + \n tm_dots(size = 0.3, col = 'black') + \n\n# Then overlay the health facility buffers in polylines\ntm_shape(sle_hf_2k) +\n tm_borders(col = \"black\", lwd = 2) +\n\n# Highlight cases that are not part of any health facility buffers\n# in red dots \ntm_shape(linelist_sf_hf_2k %>% \n filter(is.na(osm_id.y))) +\n tm_dots(size = 0.1, col='red') +\ntm_view(set.view = c(-13.2284,8.4699, 13), set.zoom.limits = c(13,14)) +\n\n# add title \ntm_layout(title = \"Cases by clinic catchment area\")\n\n\n\n\n\n\n\nOther spatial joins\nAlternative values for argument join include (from the documentation)\n\nst_contains_properly\n\nst_contains\n\nst_covered_by\n\nst_covers\n\nst_crosses\n\nst_disjoint\n\nst_equals_exact\n\nst_equals\n\nst_is_within_distance\n\nst_nearest_feature\n\nst_overlaps\n\nst_touches\n\nst_within", "crumbs": [ "Analysis", "28  GIS basics" @@ -2496,7 +2496,7 @@ "href": "new_pages/gis.html#choropleth-maps", "title": "28  GIS basics", "section": "28.7 Choropleth maps", - "text": "28.7 Choropleth maps\nChoropleth maps can be useful to visualize your data by pre-defined area, usually administrative unit or health area. In outbreak response this can help to target resource allocation for specific areas with high incidence rates, for example.\nNow that we have the administrative unit names assigned to all cases (see section on spatial joins, above), we can start mapping the case counts by area (choropleth maps).\nSince we also have population data by ADM3, we can add this information to the case_adm3 table created previously.\nWe begin with the dataframe created in the previous step case_adm3, which is a summary table of each administrative unit and its number of cases.\n\nThe population data sle_adm3_pop are joined using a left_join() from dplyr on the basis of common values across column admin3pcod in the case_adm3 dataframe, and column adm_pcode in the sle_adm3_pop dataframe. See page on Joining data).\n\nselect() is applied to the new dataframe, to keep only the useful columns - total is total population\n\nCases per 10,000 populaton is calculated as a new column with mutate()\n\n\n# Add population data and calculate cases per 10K population\ncase_adm3 <- case_adm3 %>% \n left_join(sle_adm3_pop, # add columns from pop dataset\n by = c(\"admin3pcod\" = \"adm3_pcode\")) %>% # join based on common values across these two columns\n select(names(case_adm3), total) %>% # keep only important columns, including total population\n mutate(case_10kpop = round(cases/total * 10000, 3)) # make new column with case rate per 10000, rounded to 3 decimals\n\ncase_adm3 # print to console for viewing\n\n# A tibble: 10 × 5\n# Groups: admin3pcod [10]\n admin3pcod admin3name cases total case_10kpop\n <chr> <chr> <int> <int> <dbl>\n 1 SL040102 Mountain Rural 252 33993 74.1 \n 2 SL040208 West III 247 210252 11.7 \n 3 SL040207 West II 182 145109 12.5 \n 4 SL040204 East II 120 99821 12.0 \n 5 SL040201 Central I 66 69683 9.47\n 6 SL040206 West I 44 60186 7.31\n 7 SL040203 East I 43 68284 6.30\n 8 SL040205 East III 23 500134 0.46\n 9 SL040202 Central II 22 23874 9.22\n10 <NA> <NA> 1 NA NA \n\n\nJoin this table with the ADM3 polygons shapefile for mapping\n\ncase_adm3_sf <- case_adm3 %>% # begin with cases & rate by admin unit\n left_join(sle_adm3, by=\"admin3pcod\") %>% # join to shapefile data by common column\n select(objectid, admin3pcod, # keep only certain columns of interest\n admin3name = admin3name.x, # clean name of one column\n admin2name, admin1name,\n cases, total, case_10kpop,\n geometry) %>% # keep geometry so polygons can be plotted\n drop_na(objectid) %>% # drop any empty rows\n st_as_sf() # convert to shapefile\n\nMapping the results\n\n# tmap mode\ntmap_mode(\"plot\") # view static map\n\n# plot polygons\ntm_shape(case_adm3_sf) + \n tm_polygons(\"cases\") + # color by number of cases column\n tm_text(\"admin3name\") # name display\n\n\n\n\n\n\n\n\nWe can also map the incidence rates\n\n# Cases per 10K population\ntmap_mode(\"plot\") # static viewing mode\n\n# plot\ntm_shape(case_adm3_sf) + # plot polygons\n tm_polygons(\"case_10kpop\", # color by column containing case rate\n breaks=c(0, 10, 50, 100), # define break points for colors\n palette = \"Purples\" # use a purple color palette\n ) +\n tm_text(\"admin3name\") # display text", + "text": "28.7 Choropleth maps\nChoropleth maps can be useful to visualize your data by pre-defined area, usually administrative unit or health area. In outbreak response this can help to target resource allocation for specific areas with high incidence rates, for example.\nNow that we have the administrative unit names assigned to all cases (see section on spatial joins, above), we can start mapping the case counts by area (choropleth maps).\nSince we also have population data by ADM3, we can add this information to the case_adm3 table created previously.\nWe begin with the dataframe created in the previous step case_adm3, which is a summary table of each administrative unit and its number of cases.\n\nThe population data sle_adm3_pop are joined using a left_join() from dplyr on the basis of common values across column admin3pcod in the case_adm3 dataframe, and column adm_pcode in the sle_adm3_pop dataframe. See page on Joining data)\nselect() is applied to the new dataframe, to keep only the useful columns - total is total population\nCases per 10,000 populaton is calculated as a new column with mutate()\n\n\n# Add population data and calculate cases per 10K population\ncase_adm3 <- case_adm3 %>% \n left_join(sle_adm3_pop, # add columns from pop dataset\n by = c(\"admin3pcod\" = \"adm3_pcode\")) %>% # join based on common values across these two columns\n select(names(case_adm3), total) %>% # keep only important columns, including total population\n mutate(case_10kpop = round(cases/total * 10000, 3)) # make new column with case rate per 10000, rounded to 3 decimals\n\ncase_adm3 # print to console for viewing\n\n# A tibble: 10 × 5\n# Groups: admin3pcod [10]\n admin3pcod admin3name cases total case_10kpop\n <chr> <chr> <int> <int> <dbl>\n 1 SL040102 Mountain Rural 282 33993 83.0 \n 2 SL040208 West III 213 210252 10.1 \n 3 SL040207 West II 184 145109 12.7 \n 4 SL040204 East II 122 99821 12.2 \n 5 SL040203 East I 60 68284 8.79\n 6 SL040201 Central I 51 69683 7.32\n 7 SL040206 West I 48 60186 7.98\n 8 SL040202 Central II 18 23874 7.54\n 9 SL040205 East III 17 500134 0.34\n10 <NA> <NA> 5 NA NA \n\n\nJoin this table with the ADM3 polygons shapefile for mapping.\n\ncase_adm3_sf <- case_adm3 %>% # begin with cases & rate by admin unit\n left_join(sle_adm3, by = \"admin3pcod\") %>% # join to shapefile data by common column\n select(objectid, admin3pcod, # keep only certain columns of interest\n admin3name = admin3name.x, # clean name of one column\n admin2name, admin1name,\n cases, total, case_10kpop,\n geometry) %>% # keep geometry so polygons can be plotted\n drop_na(objectid) %>% # drop any empty rows\n st_as_sf() # convert to shapefile\n\nMapping the results.\n\n# tmap mode\ntmap_mode(\"plot\") # view static map\n\n# plot polygons\ntm_shape(case_adm3_sf) + \n tm_polygons(\"cases\") + # color by number of cases column\n tm_text(\"admin3name\") # name display\n\n\n\n\n\n\n\n\nWe can also map the incidence rates\n\n# Cases per 10K population\ntmap_mode(\"plot\") # static viewing mode\n\n# plot\ntm_shape(case_adm3_sf) + # plot polygons\n tm_polygons(\"case_10kpop\", # color by column containing case rate\n breaks=c(0, 10, 50, 100), # define break points for colors\n palette = \"Purples\" # use a purple color palette\n ) +\n tm_text(\"admin3name\") # display text", "crumbs": [ "Analysis", "28  GIS basics" @@ -2507,7 +2507,7 @@ "href": "new_pages/gis.html#mapping-with-ggplot2", "title": "28  GIS basics", "section": "28.8 Mapping with ggplot2", - "text": "28.8 Mapping with ggplot2\nIf you are already familiar with using ggplot2, you can use that package instead to create static maps of your data. The geom_sf() function will draw different objects based on which features (points, lines, or polygons) are in your data. For example, you can use geom_sf() in a ggplot() using sf data with polygon geometry to create a choropleth map.\nTo illustrate how this works, we can start with the ADM3 polygons shapefile that we used earlier. Recall that these are Admin Level 3 regions in Sierra Leone:\n\nsle_adm3\n\nSimple feature collection with 12 features and 19 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: -13.29894 ymin: 8.094272 xmax: -12.91333 ymax: 8.499809\nGeodetic CRS: WGS 84\n# A tibble: 12 × 20\n objectid admin3name admin3pcod admin3ref_n admin2name admin2pcod admin1name\n * <dbl> <chr> <chr> <chr> <chr> <chr> <chr> \n 1 155 Koya Rural SL040101 Koya Rural Western A… SL0401 Western \n 2 156 Mountain Ru… SL040102 Mountain R… Western A… SL0401 Western \n 3 157 Waterloo Ru… SL040103 Waterloo R… Western A… SL0401 Western \n 4 158 York Rural SL040104 York Rural Western A… SL0401 Western \n 5 159 Central I SL040201 Central I Western A… SL0402 Western \n 6 160 East I SL040203 East I Western A… SL0402 Western \n 7 161 East II SL040204 East II Western A… SL0402 Western \n 8 162 Central II SL040202 Central II Western A… SL0402 Western \n 9 163 West III SL040208 West III Western A… SL0402 Western \n10 164 West I SL040206 West I Western A… SL0402 Western \n11 165 West II SL040207 West II Western A… SL0402 Western \n12 167 East III SL040205 East III Western A… SL0402 Western \n# ℹ 13 more variables: admin1pcod <chr>, admin0name <chr>, admin0pcod <chr>,\n# date <date>, valid_on <date>, valid_to <date>, shape_leng <dbl>,\n# shape_area <dbl>, rowcacode0 <chr>, rowcacode1 <chr>, rowcacode2 <chr>,\n# rowcacode3 <chr>, geometry <MULTIPOLYGON [°]>\n\n\nWe can use the left_join() function from dplyr to add the data we would like to map to the shapefile object. In this case, we are going to use the case_adm3 data frame that we created earlier to summarize case counts by administrative region; however, we can use this same approach to map any data stored in a data frame.\n\nsle_adm3_dat <- sle_adm3 %>% \n inner_join(case_adm3, by = \"admin3pcod\") # inner join = retain only if in both data objects\n\nselect(sle_adm3_dat, admin3name.x, cases) # print selected variables to console\n\nSimple feature collection with 9 features and 2 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: -13.29894 ymin: 8.384533 xmax: -13.12612 ymax: 8.499809\nGeodetic CRS: WGS 84\n# A tibble: 9 × 3\n admin3name.x cases geometry\n <chr> <int> <MULTIPOLYGON [°]>\n1 Mountain Rural 252 (((-13.21496 8.474341, -13.21479 8.474289, -13.21465 8.4…\n2 Central I 66 (((-13.22646 8.489716, -13.22648 8.48955, -13.22644 8.48…\n3 East I 43 (((-13.2129 8.494033, -13.21076 8.494026, -13.21013 8.49…\n4 East II 120 (((-13.22653 8.491883, -13.22647 8.491853, -13.22642 8.4…\n5 Central II 22 (((-13.23154 8.491768, -13.23141 8.491566, -13.23144 8.4…\n6 West III 247 (((-13.28529 8.497354, -13.28456 8.496497, -13.28403 8.4…\n7 West I 44 (((-13.24677 8.493453, -13.24669 8.493285, -13.2464 8.49…\n8 West II 182 (((-13.25698 8.485518, -13.25685 8.485501, -13.25668 8.4…\n9 East III 23 (((-13.20465 8.485758, -13.20461 8.485698, -13.20449 8.4…\n\n\nTo make a column chart of case counts by region, using ggplot2, we could then call geom_col() as follows:\n\nggplot(data=sle_adm3_dat) +\n geom_col(aes(x=fct_reorder(admin3name.x, cases, .desc=T), # reorder x axis by descending 'cases'\n y=cases)) + # y axis is number of cases by region\n theme_bw() +\n labs( # set figure text\n title=\"Number of cases, by administrative unit\",\n x=\"Admin level 3\",\n y=\"Number of cases\"\n ) + \n guides(x=guide_axis(angle=45)) # angle x-axis labels 45 degrees to fit better\n\n\n\n\n\n\n\n\nIf we want to use ggplot2 to instead make a choropleth map of case counts, we can use similar syntax to call the geom_sf() function:\n\nggplot(data=sle_adm3_dat) + \n geom_sf(aes(fill=cases)) # set fill to vary by case count variable\n\n\n\n\n\n\n\n\nWe can then customize the appearance of our map using grammar that is consistent across ggplot2, for example:\n\nggplot(data=sle_adm3_dat) + \n geom_sf(aes(fill=cases)) + \n scale_fill_continuous(high=\"#54278f\", low=\"#f2f0f7\") + # change color gradient\n theme_bw() +\n labs(title = \"Number of cases, by administrative unit\", # set figure text\n subtitle = \"Admin level 3\"\n )\n\n\n\n\n\n\n\n\nFor R users who are comfortable working with ggplot2, geom_sf() offers a simple and direct implementation that is suitable for basic map visualizations. To learn more, read the geom_sf() vignette or the ggplot2 book.", + "text": "28.8 Mapping with ggplot2\nIf you are already familiar with using ggplot2, you can use that package instead to create static maps of your data. The geom_sf() function will draw different objects based on which features (points, lines, or polygons) are in your data. For example, you can use geom_sf() in a ggplot() using sf data with polygon geometry to create a choropleth map.\nTo illustrate how this works, we can start with the ADM3 polygons shapefile that we used earlier. Recall that these are Admin Level 3 regions in Sierra Leone:\n\nsle_adm3\n\nSimple feature collection with 12 features and 19 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: -13.29894 ymin: 8.094272 xmax: -12.91333 ymax: 8.499809\nGeodetic CRS: WGS 84\n# A tibble: 12 × 20\n objectid admin3name admin3pcod admin3ref_n admin2name admin2pcod admin1name\n * <dbl> <chr> <chr> <chr> <chr> <chr> <chr> \n 1 155 Koya Rural SL040101 Koya Rural Western A… SL0401 Western \n 2 156 Mountain Ru… SL040102 Mountain R… Western A… SL0401 Western \n 3 157 Waterloo Ru… SL040103 Waterloo R… Western A… SL0401 Western \n 4 158 York Rural SL040104 York Rural Western A… SL0401 Western \n 5 159 Central I SL040201 Central I Western A… SL0402 Western \n 6 160 East I SL040203 East I Western A… SL0402 Western \n 7 161 East II SL040204 East II Western A… SL0402 Western \n 8 162 Central II SL040202 Central II Western A… SL0402 Western \n 9 163 West III SL040208 West III Western A… SL0402 Western \n10 164 West I SL040206 West I Western A… SL0402 Western \n11 165 West II SL040207 West II Western A… SL0402 Western \n12 167 East III SL040205 East III Western A… SL0402 Western \n# ℹ 13 more variables: admin1pcod <chr>, admin0name <chr>, admin0pcod <chr>,\n# date <date>, valid_on <date>, valid_to <date>, shape_leng <dbl>,\n# shape_area <dbl>, rowcacode0 <chr>, rowcacode1 <chr>, rowcacode2 <chr>,\n# rowcacode3 <chr>, geometry <MULTIPOLYGON [°]>\n\n\nWe can use the left_join() function from dplyr to add the data we would like to map to the shapefile object. In this case, we are going to use the case_adm3 data frame that we created earlier to summarize case counts by administrative region; however, we can use this same approach to map any data stored in a data frame.\n\nsle_adm3_dat <- sle_adm3 %>% \n inner_join(case_adm3, by = \"admin3pcod\") # inner join = retain only if in both data objects\n\nselect(sle_adm3_dat, admin3name.x, cases) # print selected variables to console\n\nSimple feature collection with 9 features and 2 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: -13.29894 ymin: 8.384533 xmax: -13.12612 ymax: 8.499809\nGeodetic CRS: WGS 84\n# A tibble: 9 × 3\n admin3name.x cases geometry\n <chr> <int> <MULTIPOLYGON [°]>\n1 Mountain Rural 282 (((-13.21496 8.474341, -13.21479 8.474289, -13.21465 8.4…\n2 Central I 51 (((-13.22646 8.489716, -13.22648 8.48955, -13.22644 8.48…\n3 East I 60 (((-13.2129 8.494033, -13.21076 8.494026, -13.21013 8.49…\n4 East II 122 (((-13.22653 8.491883, -13.22647 8.491853, -13.22642 8.4…\n5 Central II 18 (((-13.23154 8.491768, -13.23141 8.491566, -13.23144 8.4…\n6 West III 213 (((-13.28529 8.497354, -13.28456 8.496497, -13.28403 8.4…\n7 West I 48 (((-13.24677 8.493453, -13.24669 8.493285, -13.2464 8.49…\n8 West II 184 (((-13.25698 8.485518, -13.25685 8.485501, -13.25668 8.4…\n9 East III 17 (((-13.20465 8.485758, -13.20461 8.485698, -13.20449 8.4…\n\n\nTo make a column chart of case counts by region, using ggplot2, we could then call geom_col() as follows:\n\nggplot(data = sle_adm3_dat) +\n geom_col(aes(x = fct_reorder(admin3name.x, cases, .desc=T), # reorder x axis by descending 'cases'\n y = cases)) + # y axis is number of cases by region\n theme_bw() +\n labs( # set figure text\n title=\"Number of cases, by administrative unit\",\n x=\"Admin level 3\",\n y=\"Number of cases\"\n ) + \n guides(x = guide_axis(angle = 45)) # angle x-axis labels 45 degrees to fit better\n\n\n\n\n\n\n\n\nIf we want to use ggplot2 to instead make a choropleth map of case counts, we can use similar syntax to call the geom_sf() function:\n\nggplot(data = sle_adm3_dat) + \n geom_sf(aes(fill = cases)) # set fill to vary by case count variable\n\n\n\n\n\n\n\n\nWe can then customize the appearance of our map using grammar that is consistent across ggplot2, for example:\n\nggplot(data = sle_adm3_dat) + \n geom_sf(aes(fill = cases)) + \n scale_fill_continuous(high = \"#54278f\", low = \"#f2f0f7\") + # change color gradient\n theme_bw() +\n labs(title = \"Number of cases, by administrative unit\", # set figure text\n subtitle = \"Admin level 3\"\n )\n\n\n\n\n\n\n\n\nFor R users who are comfortable working with ggplot2, geom_sf() offers a simple and direct implementation that is suitable for basic map visualizations. To learn more, read the geom_sf() vignette or the ggplot2 book.", "crumbs": [ "Analysis", "28  GIS basics" @@ -2518,7 +2518,7 @@ "href": "new_pages/gis.html#basemaps", "title": "28  GIS basics", "section": "28.9 Basemaps", - "text": "28.9 Basemaps\n\nOpenStreetMap\nBelow we describe how to achieve a basemap for a ggplot2 map using OpenStreetMap features. Alternative methods include using ggmap which requires free registration with Google (details).\nOpenStreetMap is a collaborative project to create a free editable map of the world. The underlying geolocation data (e.g. locations of cities, roads, natural features, airports, schools, hospitals, roads etc) are considered the primary output of the project.\nFirst we load the OpenStreetMap package, from which we will get our basemap.\nThen, we create the object map, which we define using the function openmap() from OpenStreetMap package (documentation). We provide the following:\n\nupperLeft and lowerRight Two coordinate pairs specifying the limits of the basemap tile\n\nIn this case we’ve put in the max and min from the linelist rows, so the map will respond dynamically to the data\n\n\nzoom = (if null it is determined automatically)\n\ntype = which type of basemap - we have listed several possibilities here and the code is currently using the first one ([1]) “osm”\n\nmergeTiles = we chose TRUE so the basetiles are all merged into one\n\n\n# load package\npacman::p_load(OpenStreetMap)\n\n# Fit basemap by range of lat/long coordinates. Choose tile type\nmap <- OpenStreetMap::openmap(\n upperLeft = c(max(linelist$lat, na.rm=T), max(linelist$lon, na.rm=T)), # limits of basemap tile\n lowerRight = c(min(linelist$lat, na.rm=T), min(linelist$lon, na.rm=T)),\n zoom = NULL,\n type = c(\"osm\", \"stamen-toner\", \"stamen-terrain\", \"stamen-watercolor\", \"esri\",\"esri-topo\")[1])\n\nIf we plot this basemap right now, using autoplot.OpenStreetMap() from OpenStreetMap package, you see that the units on the axes are not latitude/longitude coordinates. It is using a different coordinate system. To correctly display the case residences (which are stored in lat/long), this must be changed.\n\nautoplot.OpenStreetMap(map)\n\n\n\n\n\n\n\n\nThus, we want to convert the map to latitude/longitude with the openproj() function from OpenStreetMap package. We provide the basemap map and also provide the Coordinate Reference System (CRS) we want. We do this by providing the “proj.4” character string for the WGS 1984 projection, but you can provide the CRS in other ways as well. (see this page to better understand what a proj.4 string is)\n\n# Projection WGS84\nmap_latlon <- openproj(map, projection = \"+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs\")\n\nNow when we create the plot we see that along the axes are latitude and longitude coordinate. The coordinate system has been converted. Now our cases will plot correctly if overlaid!\n\n# Plot map. Must use \"autoplot\" in order to work with ggplot\nautoplot.OpenStreetMap(map_latlon)\n\n\n\n\n\n\n\n\nSee the tutorials here and here for more info.", + "text": "28.9 Basemaps\n\nOpenStreetMap\nBelow we describe how to achieve a basemap for a ggplot2 map using OpenStreetMap features. Alternative methods include using ggmap which requires free registration with Google (details).\nOpenStreetMap is a collaborative project to create a free editable map of the world. The underlying geolocation data (e.g. locations of cities, roads, natural features, airports, schools, hospitals, roads etc) are considered the primary output of the project.\nTo do this, first we load in the packages we’ll need. These are the maptiles package, which we will use to get the OpenStreetMap base layer, and the tidyterra package for plotting the maptiles object.\nThe function get_tiles() from the maptiles package can accept a variety of different inputs. These include shapefiles, as an sf object, bbox objects and SpatExtent objects. For a full list, type ?get_tiles into your console.\nThere are a number of different ways to customise the output of get_tiles(), including changing the map provider, and saving the output so it can be accessed offline (by specifying a folder location in the cachedir = argument).\nHere we are going to create a map using the coordinates of the area we are interested in. To provide these in a format that can be used by get_tiles() we will wrap the coordinates with the function ext() from the terra package, which should be loaded with tidyterra. Then we will use the function geom_spatraster_rgb() to display the map.\nNote: If you right click on Google Maps it will display the coordinates of the point.\n\n# load package\npacman::p_load(\n maptiles,\n tidyterra\n)\n\n#The coordinate extent of the area we are looking at, by taking the range of longditude and latitudes\n#Values correspond as xmin, xmax, ymin, ymax\ncoordinates <- c(min(linelist$lon), \n max(linelist$lon), \n min(linelist$lat), \n max(linelist$lat))\n\n#Get the basemap\nbasemap <- get_tiles(terra::ext(coordinates),\n crop = T, project = T)\n\n# Plot the tile\nggplot() +\n geom_spatraster_rgb(\n data = basemap\n )", "crumbs": [ "Analysis", "28  GIS basics" @@ -2529,7 +2529,7 @@ "href": "new_pages/gis.html#contoured-density-heatmaps", "title": "28  GIS basics", "section": "28.10 Contoured density heatmaps", - "text": "28.10 Contoured density heatmaps\nBelow we describe how to achieve a contoured density heatmap of cases, over a basemap, beginning with a linelist (one row per case).\n\nCreate basemap tile from OpenStreetMap, as described above\n\nPlot the cases from linelist using the latitude and longitude columns\n\nConvert the points to a density heatmap with stat_density_2d() from ggplot2,\n\nWhen we have a basemap with lat/long coordinates, we can plot our cases on top using the lat/long coordinates of their residence.\nBuilding on the function autoplot.OpenStreetMap() to create the basemap, ggplot2 functions will easily add on top, as shown with geom_point() below:\n\n# Plot map. Must be autoplotted to work with ggplot\nautoplot.OpenStreetMap(map_latlon)+ # begin with the basemap\n geom_point( # add xy points from linelist lon and lat columns \n data = linelist, \n aes(x = lon, y = lat),\n size = 1, \n alpha = 0.5,\n show.legend = FALSE) + # drop legend entirely\n labs(x = \"Longitude\", # titles & labels\n y = \"Latitude\",\n title = \"Cumulative cases\")\n\n\n\n\n\n\n\n\nThe map above might be difficult to interpret, especially with the points overlapping. So you can instead plot a 2d density map using the ggplot2 function stat_density_2d(). You are still using the linelist lat/lon coordinates, but a 2D kernel density estimation is performed and the results are displayed with contour lines - like a topographical map. Read the full documentation here.\n\n# begin with the basemap\nautoplot.OpenStreetMap(map_latlon)+\n \n # add the density plot\n ggplot2::stat_density_2d(\n data = linelist,\n aes(\n x = lon,\n y = lat,\n fill = ..level..,\n alpha = ..level..),\n bins = 10,\n geom = \"polygon\",\n contour_var = \"count\",\n show.legend = F) + \n \n # specify color scale\n scale_fill_gradient(low = \"black\", high = \"red\")+\n \n # labels \n labs(x = \"Longitude\",\n y = \"Latitude\",\n title = \"Distribution of cumulative cases\")\n\n\n\n\n\n\n\n\n\n\nTime series heatmap\nThe density heatmap above shows cumulative cases. We can examine the outbreak over time and space by faceting the heatmap based on the month of symptom onset, as derived from the linelist.\nWe begin in the linelist, creating a new column with the Year and Month of onset. The format() function from base R changes how a date is displayed. In this case we want “YYYY-MM”.\n\n# Extract month of onset\nlinelist <- linelist %>% \n mutate(date_onset_ym = format(date_onset, \"%Y-%m\"))\n\n# Examine the values \ntable(linelist$date_onset_ym, useNA = \"always\")\n\n\n2014-04 2014-05 2014-06 2014-07 2014-08 2014-09 2014-10 2014-11 2014-12 2015-01 \n 1 6 20 36 92 192 175 134 108 80 \n2015-02 2015-03 2015-04 <NA> \n 52 40 31 33 \n\n\nNow, we simply introduce facetting via ggplot2 to the density heatmap. facet_wrap() is applied, using the new column as rows. We set the number of facet columns to 3 for clarity.\n\n# packages\npacman::p_load(OpenStreetMap, tidyverse)\n\n# begin with the basemap\nautoplot.OpenStreetMap(map_latlon)+\n \n # add the density plot\n ggplot2::stat_density_2d(\n data = linelist,\n aes(\n x = lon,\n y = lat,\n fill = ..level..,\n alpha = ..level..),\n bins = 10,\n geom = \"polygon\",\n contour_var = \"count\",\n show.legend = F) + \n \n # specify color scale\n scale_fill_gradient(low = \"black\", high = \"red\")+\n \n # labels \n labs(x = \"Longitude\",\n y = \"Latitude\",\n title = \"Distribution of cumulative cases over time\")+\n \n # facet the plot by month-year of onset\n facet_wrap(~ date_onset_ym, ncol = 4)", + "text": "28.10 Contoured density heatmaps\nBelow we describe how to achieve a contoured density heatmap of cases, over a basemap, beginning with a linelist (one row per case).\n\nCreate basemap tile, as described above\nPlot the cases from linelist using the latitude and longitude columns\nConvert the points to a density heatmap with stat_density_2d() from ggplot2\n\nWhen we have a basemap with lat/long coordinates, we can plot our cases on top using the lat/long coordinates of their residence.\nBuilding on the function autoplot.OpenStreetMap() to create the basemap, ggplot2 functions will easily add on top, as shown with geom_point() below:\n\n# Plot map. Must be autoplotted to work with ggplot\nggplot() +\n geom_spatraster_rgb(\n data = basemap\n ) + \n geom_point( # add xy points from linelist lon and lat columns \n data = linelist, \n aes(x = lon, y = lat),\n size = 1, \n alpha = 0.5,\n show.legend = FALSE) + # drop legend entirely\n labs(x = \"Longitude\", # titles & labels\n y = \"Latitude\",\n title = \"Cumulative cases\")\n\n\n\n\n\n\n\n\nThe map above might be difficult to interpret, especially with the points overlapping. So you can instead plot a 2d density map using the ggplot2 function stat_density_2d(). You are still using the linelist lat/lon coordinates, but a 2D kernel density estimation is performed and the results are displayed with contour lines - like a topographical map. Read the full documentation here.\n\n# begin with the basemap\nggplot() +\n geom_spatraster_rgb(\n data = basemap\n ) +\n # add the density plot\n ggplot2::stat_density_2d(\n data = linelist,\n aes(\n x = lon,\n y = lat,\n fill = ..level..,\n alpha = ..level..),\n bins = 10,\n geom = \"polygon\",\n contour_var = \"count\",\n show.legend = F) + \n \n # specify color scale\n scale_fill_gradient(low = \"black\", high = \"red\") +\n \n # labels \n labs(x = \"Longitude\",\n y = \"Latitude\",\n title = \"Distribution of cumulative cases\")\n\n\n\n\n\n\n\n\n\n\nTime series heatmap\nThe density heatmap above shows cumulative cases. We can examine the outbreak over time and space by faceting the heatmap based on the month of symptom onset, as derived from the linelist.\nWe begin in the linelist, creating a new column with the Year and Month of onset. The format() function from base R changes how a date is displayed. In this case we want “YYYY-MM”.\n\n# Extract month of onset\nlinelist <- linelist %>% \n mutate(date_onset_ym = format(date_onset, \"%Y-%m\"))\n\n# Examine the values \ntable(linelist$date_onset_ym, useNA = \"always\")\n\n\n2014-04 2014-05 2014-06 2014-07 2014-08 2014-09 2014-10 2014-11 2014-12 2015-01 \n 2 14 14 43 72 184 176 148 94 76 \n2015-02 2015-03 2015-04 <NA> \n 48 50 35 44 \n\n\nNow, we simply introduce facetting via ggplot2 to the density heatmap. facet_wrap() is applied, using the new column as rows. We set the number of facet columns to 3 for clarity.\n\n# begin with the basemap\nggplot() +\n geom_spatraster_rgb(\n data = basemap\n ) + \n # add the density plot\n ggplot2::stat_density_2d(\n data = linelist,\n aes(\n x = lon,\n y = lat,\n fill = ..level..,\n alpha = ..level..),\n bins = 10,\n geom = \"polygon\",\n contour_var = \"count\",\n show.legend = F) + \n \n # specify color scale\n scale_fill_gradient(low = \"black\", high = \"red\") +\n \n # labels \n labs(x = \"Longitude\",\n y = \"Latitude\",\n title = \"Distribution of cumulative cases over time\") +\n \n # facet the plot by month-year of onset\n facet_wrap(~ date_onset_ym, ncol = 4)", "crumbs": [ "Analysis", "28  GIS basics" @@ -2540,7 +2540,7 @@ "href": "new_pages/gis.html#spatial-statistics", "title": "28  GIS basics", "section": "28.11 Spatial statistics", - "text": "28.11 Spatial statistics\nMost of our discussion so far has focused on visualization of spatial data. In some cases, you may also be interested in using spatial statistics to quantify the spatial relationships of attributes in your data. This section will provide a very brief overview of some key concepts in spatial statistics, and suggest some resources that will be helpful to explore if you wish to do more comprehensive spatial analyses.\n\nSpatial relationships\nBefore we can calculate any spatial statistics, we need to specify the relationships between features in our data. There are many ways to conceptualize spatial relationships, but a simple and commonly-applicable model to use is that of adjacency - specifically, that we expect a geographic relationship between areas that share a border or “neighbour” one another.\nWe can quantify adjacency relationships between administrative region polygons in the sle_adm3 data we have been using with the spdep package. We will specify queen contiguity, which means that regions will be neighbors if they share at least one point along their borders. The alternative would be rook contiguity, which requires that regions share an edge - in our case, with irregular polygons, the distinction is trivial, but in some cases the choice between queen and rook can be influential.\n\nsle_nb <- spdep::poly2nb(sle_adm3_dat, queen=T) # create neighbors \nsle_adjmat <- spdep::nb2mat(sle_nb) # create matrix summarizing neighbor relationships\nsle_listw <- spdep::nb2listw(sle_nb) # create listw (list of weights) object -- we will need this later\n\nsle_nb\n\nNeighbour list object:\nNumber of regions: 9 \nNumber of nonzero links: 30 \nPercentage nonzero weights: 37.03704 \nAverage number of links: 3.333333 \n\nround(sle_adjmat, digits = 2)\n\n [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]\n1 0.00 0.20 0.00 0.20 0.00 0.2 0.00 0.20 0.20\n2 0.25 0.00 0.00 0.25 0.25 0.0 0.00 0.25 0.00\n3 0.00 0.00 0.00 0.50 0.00 0.0 0.00 0.00 0.50\n4 0.25 0.25 0.25 0.00 0.00 0.0 0.00 0.00 0.25\n5 0.00 0.33 0.00 0.00 0.00 0.0 0.33 0.33 0.00\n6 0.50 0.00 0.00 0.00 0.00 0.0 0.00 0.50 0.00\n7 0.00 0.00 0.00 0.00 0.50 0.0 0.00 0.50 0.00\n8 0.20 0.20 0.00 0.00 0.20 0.2 0.20 0.00 0.00\n9 0.33 0.00 0.33 0.33 0.00 0.0 0.00 0.00 0.00\nattr(,\"call\")\nspdep::nb2mat(neighbours = sle_nb)\n\n\nThe matrix printed above shows the relationships between the 9 regions in our sle_adm3 data. A score of 0 indicates two regions are not neighbors, while any value other than 0 indicates a neighbor relationship. The values in the matrix are scaled so that each region has a total row weight of 1.\nA better way to visualize these neighbor relationships is by plotting them:\n\nplot(sle_adm3_dat$geometry) + # plot region boundaries\n spdep::plot.nb(sle_nb,as(sle_adm3_dat, 'Spatial'), col='grey', add=T) # add neighbor relationships\n\n\n\n\n\n\n\n\nWe have used an adjacency approach to identify neighboring polygons; the neighbors we identified are also sometimes called contiguity-based neighbors. But this is just one way of choosing which regions are expected to have a geographic relationship. The most common alternative approaches for identifying geographic relationships generate distance-based neighbors; briefly, these are:\n\nK-nearest neighbors - Based on the distance between centroids (the geographically-weighted center of each polygon region), select the n closest regions as neighbors. A maximum-distance proximity threshold may also be specified. In spdep, you can use knearneigh() (see documentation).\nDistance threshold neighbors - Select all neighbors within a distance threshold. In spdep, these neighbor relationships can be identified using dnearneigh() (see documentation).\n\n\n\nSpatial autocorrelation\nTobler’s oft-cited first law of geography states that “everything is related to everything else, but near things are more related than distant things.” In epidemiology, this often means that risk of a particular health outcome in a given region is more similar to its neighboring regions than to those far away. This concept has been formalized as spatial autocorrelation - the statistical property that geographic features with similar values are clustered together in space. Statistical measures of spatial autocorrelation can be used to quantify the extent of spatial clustering in your data, locate where clustering occurs, and identify shared patterns of spatial autocorrelation between distinct variables in your data. This section gives an overview of some common measures of spatial autocorrelation and how to calculate them in R.\nMoran’s I - This is a global summary statistic of the correlation between the value of a variable in one region, and the values of the same variable in neighboring regions. The Moran’s I statistic typically ranges from -1 to 1. A value of 0 indicates no pattern of spatial correlation, while values closer to 1 or -1 indicate stronger spatial autocorrelation (similar values close together) or spatial dispersion (dissimilar values close together), respectively.\nFor an example, we will calculate a Moran’s I statistic to quantify the spatial autocorrelation in Ebola cases we mapped earlier (remember, this is a subset of cases from the simulated epidemic linelist dataframe). The spdep package has a function, moran.test, that can do this calculation for us:\n\nmoran_i <-spdep::moran.test(sle_adm3_dat$cases, # numeric vector with variable of interest\n listw=sle_listw) # listw object summarizing neighbor relationships\n\nmoran_i # print results of Moran's I test\n\n\n Moran I test under randomisation\n\ndata: sle_adm3_dat$cases \nweights: sle_listw \n\nMoran I statistic standard deviate = 1.7943, p-value = 0.03639\nalternative hypothesis: greater\nsample estimates:\nMoran I statistic Expectation Variance \n 0.2605303 -0.1250000 0.0461688 \n\n\nThe output from the moran.test() function shows us a Moran I statistic of round(moran_i$estimate[1],2). This indicates the presence of spatial autocorrelation in our data - specifically, that regions with similar numbers of Ebola cases are likely to be close together. The p-value provided by moran.test() is generated by comparison to the expectation under null hypothesis of no spatial autocorrelation, and can be used if you need to report the results of a formal hypothesis test.\nLocal Moran’s I - We can decompose the (global) Moran’s I statistic calculated above to identify localized spatial autocorrelation; that is, to identify specific clusters in our data. This statistic, which is sometimes called a Local Indicator of Spatial Association (LISA) statistic, summarizes the extent of spatial autocorrelation around each individual region. It can be useful for finding “hot” and “cold” spots on the map.\nTo show an example, we can calculate and map Local Moran’s I for the Ebola case counts used above, with the local_moran() function from spdep:\n\n# calculate local Moran's I\nlocal_moran <- spdep::localmoran( \n sle_adm3_dat$cases, # variable of interest\n listw=sle_listw # listw object with neighbor weights\n)\n\n# join results to sf data\nsle_adm3_dat<- cbind(sle_adm3_dat, local_moran) \n\n# plot map\nggplot(data=sle_adm3_dat) +\n geom_sf(aes(fill=Ii)) +\n theme_bw() +\n scale_fill_gradient2(low=\"#2c7bb6\", mid=\"#ffffbf\", high=\"#d7191c\",\n name=\"Local Moran's I\") +\n labs(title=\"Local Moran's I statistic for Ebola cases\",\n subtitle=\"Admin level 3 regions, Sierra Leone\")\n\n\n\n\n\n\n\n\nGetis-Ord Gi* - This is another statistic that is commonly used for hotspot analysis; in large part, the popularity of this statistic relates to its use in the Hot Spot Analysis tool in ArcGIS. It is based on the assumption that typically, the difference in a variable’s value between neighboring regions should follow a normal distribution. It uses a z-score approach to identify regions that have significantly higher (hot spot) or significantly lower (cold spot) values of a specified variable, compared to their neighbors.\nWe can calculate and map the Gi* statistic using the localG() function from spdep:\n\n# Perform local G analysis\ngetis_ord <- spdep::localG(\n sle_adm3_dat$cases,\n sle_listw\n)\n\n# join results to sf data\nsle_adm3_dat$getis_ord <- as.numeric(getis_ord)\n\n# plot map\nggplot(data=sle_adm3_dat) +\n geom_sf(aes(fill=getis_ord)) +\n theme_bw() +\n scale_fill_gradient2(low=\"#2c7bb6\", mid=\"#ffffbf\", high=\"#d7191c\",\n name=\"Gi*\") +\n labs(title=\"Getis-Ord Gi* statistic for Ebola cases\",\n subtitle=\"Admin level 3 regions, Sierra Leone\")\n\n\n\n\n\n\n\n\nAs you can see, the map of Getis-Ord Gi* looks slightly different from the map of Local Moran’s I produced earlier. This reflects that the method used to calculate these two statistics are slightly different; which one you should use depends on your specific use case and the research question of interest.\nLee’s L test - This is a statistical test for bivariate spatial correlation. It allows you to test whether the spatial pattern for a given variable x is similar to the spatial pattern of another variable, y, that is hypothesized to be related spatially to x.\nTo give an example, let’s test whether the spatial pattern of Ebola cases from the simulated epidemic is correlated with the spatial pattern of population. To start, we need to have a population variable in our sle_adm3 data. We can use the total variable from the sle_adm3_pop dataframe that we loaded earlier.\n\nsle_adm3_dat <- sle_adm3_dat %>% \n rename(population = total) # rename 'total' to 'population'\n\nWe can quickly visualize the spatial patterns of the two variables side by side, to see whether they look similar:\n\ntmap_mode(\"plot\")\n\ncases_map <- tm_shape(sle_adm3_dat) + tm_polygons(\"cases\") + tm_layout(main.title=\"Cases\")\npop_map <- tm_shape(sle_adm3_dat) + tm_polygons(\"population\") + tm_layout(main.title=\"Population\")\n\ntmap_arrange(cases_map, pop_map, ncol=2) # arrange into 2x1 facets\n\n\n\n\n\n\n\n\nVisually, the patterns seem dissimilar. We can use the lee.test() function in spdep to test statistically whether the pattern of spatial autocorrelation in the two variables is related. The L statistic will be close to 0 if there is no correlation between the patterns, close to 1 if there is a strong positive correlation (i.e. the patterns are similar), and close to -1 if there is a strong negative correlation (i.e. the patterns are inverse).\n\nlee_test <- spdep::lee.test(\n x=sle_adm3_dat$cases, # variable 1 to compare\n y=sle_adm3_dat$population, # variable 2 to compare\n listw=sle_listw # listw object with neighbor weights\n)\n\nlee_test\n\n\n Lee's L statistic randomisation\n\ndata: sle_adm3_dat$cases , sle_adm3_dat$population \nweights: sle_listw \n\nLee's L statistic standard deviate = -0.86794, p-value = 0.8073\nalternative hypothesis: greater\nsample estimates:\nLee's L statistic Expectation Variance \n -0.12740001 -0.03092239 0.01235574 \n\n\nThe output above shows that the Lee’s L statistic for our two variables was round(lee_test$estimate[1],2), which indicates weak negative correlation. This confirms our visual assessment that the pattern of cases and population are not related to one another, and provides evidence that the spatial pattern of cases is not strictly a result of population density in high-risk areas.\nThe Lee L statistic can be useful for making these kinds of inferences about the relationship between spatially distributed variables; however, to describe the nature of the relationship between two variables in more detail, or adjust for confounding, spatial regression techniques will be needed. These are described briefly in the following section.\n\n\nSpatial regression\nYou may wish to make statistical inferences about the relationships between variables in your spatial data. In these cases, it is useful to consider spatial regression techniques - that is, approaches to regression that explicitly consider the spatial organization of units in your data. Some reasons that you may need to consider spatial regression models, rather than standard regression models such as GLMs, include:\n\nStandard regression models assume that residuals are independent from one another. In the presence of strong spatial autocorrelation, the residuals of a standard regression model are likely to be spatially autocorrelated as well, thus violating this assumption. This can lead to problems with interpreting the model results, in which case a spatial model would be preferred.\nRegression models also typically assume that the effect of a variable x is constant over all observations. In the case of spatial heterogeneity, the effects we wish to estimate may vary over space, and we may be interested in quantifying those differences. In this case, spatial regression models offer more flexibility for estimating and interpreting effects.\n\nThe details of spatial regression approaches are beyond the scope of this handbook. This section will instead provide an overview of the most common spatial regression models and their uses, and refer you to references that may of use if you wish to explore this area further.\nSpatial error models - These models assume that the error terms across spatial units are correlated, in which case the data would violate the assumptions of a standard OLS model. Spatial error models are also sometimes referred to as simultaneous autoregressive (SAR) models. They can be fit using the errorsarlm() function in the spatialreg package (spatial regression functions which used to be a part of spdep).\nSpatial lag models - These models assume that the dependent variable for a region i is influenced not only by value of independent variables in i, but also by the values of those variables in regions neighboring i. Like spatial error models, spatial lag models are also sometimes described as simultaneous autoregressive (SAR) models. They can be fit using the lagsarlm() function in the spatialreg package.\nThe spdep package contains several useful diagnostic tests for deciding between standard OLS, spatial lag, and spatial error models. These tests, called Lagrange Multiplier diagnostics, can be used to identify the type of spatial dependence in your data and choose which model is most appropriate. The function lm.LMtests() can be used to calculate all of the Lagrange Multiplier tests. Anselin (1988) also provides a useful flow chart tool to decide which spatial regression model to use based on the results of the Lagrange Multiplier tests:\n\n\n\n\n\n\n\n\n\nBayesian hierarchical models - Bayesian approaches are commonly used for some applications in spatial analysis, most commonly for disease mapping. They are preferred in cases where case data are sparsely distributed (for example, in the case of a rare outcome) or statistically “noisy”, as they can be used to generate “smoothed” estimates of disease risk by accounting for the underlying latent spatial process. This may improve the quality of estimates. They also allow investigator pre-specification (via choice of prior) of complex spatial correlation patterns that may exist in the data, which can account for spatially-dependent and -independent variation in both independent and dependent variables. In R, Bayesian hierarchical models can be fit using the CARbayes package (see vignette) or R-INLA (see website and textbook). R can also be used to call external software that does Bayesian estimation, such as JAGS or WinBUGS.", + "text": "28.11 Spatial statistics\nMost of our discussion so far has focused on visualization of spatial data. In some cases, you may also be interested in using spatial statistics to quantify the spatial relationships of attributes in your data. This section will provide a very brief overview of some key concepts in spatial statistics, and suggest some resources that will be helpful to explore if you wish to do more comprehensive spatial analyses.\n\nSpatial relationships\nBefore we can calculate any spatial statistics, we need to specify the relationships between features in our data. There are many ways to conceptualize spatial relationships, but a simple and commonly-applicable model to use is that of adjacency - specifically, that we expect a geographic relationship between areas that share a border or “neighbour” one another.\nWe can quantify adjacency relationships between administrative region polygons in the sle_adm3 data we have been using with the spdep package. We will specify queen contiguity, which means that regions will be neighbors if they share at least one point along their borders. The alternative would be rook contiguity, which requires that regions share an edge - in our case, with irregular polygons, the distinction is trivial, but in some cases the choice between queen and rook can be influential.\n\nsle_nb <- spdep::poly2nb(sle_adm3_dat, queen = T) # create neighbors \nsle_adjmat <- spdep::nb2mat(sle_nb) # create matrix summarizing neighbor relationships\nsle_listw <- spdep::nb2listw(sle_nb) # create listw (list of weights) object -- we will need this later\n\nsle_nb\n\nNeighbour list object:\nNumber of regions: 9 \nNumber of nonzero links: 30 \nPercentage nonzero weights: 37.03704 \nAverage number of links: 3.333333 \n\nround(sle_adjmat, digits = 2)\n\n [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]\n1 0.00 0.20 0.00 0.20 0.00 0.2 0.00 0.20 0.20\n2 0.25 0.00 0.00 0.25 0.25 0.0 0.00 0.25 0.00\n3 0.00 0.00 0.00 0.50 0.00 0.0 0.00 0.00 0.50\n4 0.25 0.25 0.25 0.00 0.00 0.0 0.00 0.00 0.25\n5 0.00 0.33 0.00 0.00 0.00 0.0 0.33 0.33 0.00\n6 0.50 0.00 0.00 0.00 0.00 0.0 0.00 0.50 0.00\n7 0.00 0.00 0.00 0.00 0.50 0.0 0.00 0.50 0.00\n8 0.20 0.20 0.00 0.00 0.20 0.2 0.20 0.00 0.00\n9 0.33 0.00 0.33 0.33 0.00 0.0 0.00 0.00 0.00\nattr(,\"call\")\nspdep::nb2mat(neighbours = sle_nb)\n\n\nThe matrix printed above shows the relationships between the 9 regions in our sle_adm3 data. A score of 0 indicates two regions are not neighbors, while any value other than 0 indicates a neighbor relationship. The values in the matrix are scaled so that each region has a total row weight of 1.\nA better way to visualize these neighbor relationships is by plotting them:\n\nplot(sle_adm3_dat$geometry) + # plot region boundaries\n spdep::plot.nb(sle_nb,as(sle_adm3_dat, 'Spatial'), col = 'grey', add = T) # add neighbor relationships\n\n\n\n\n\n\n\n\nWe have used an adjacency approach to identify neighboring polygons; the neighbors we identified are also sometimes called contiguity-based neighbors. But this is just one way of choosing which regions are expected to have a geographic relationship. The most common alternative approaches for identifying geographic relationships generate distance-based neighbors; briefly, these are:\n\nK-nearest neighbors - Based on the distance between centroids (the geographically-weighted center of each polygon region), select the n closest regions as neighbors. A maximum-distance proximity threshold may also be specified. In spdep, you can use knearneigh() (see documentation)\nDistance threshold neighbors - Select all neighbors within a distance threshold. In spdep, these neighbor relationships can be identified using dnearneigh() (see documentation)\n\n\n\nSpatial autocorrelation\nTobler’s oft-cited first law of geography states that “everything is related to everything else, but near things are more related than distant things.” In epidemiology, this often means that risk of a particular health outcome in a given region is more similar to its neighboring regions than to those far away. This concept has been formalized as spatial autocorrelation - the statistical property that geographic features with similar values are clustered together in space. Statistical measures of spatial autocorrelation can be used to quantify the extent of spatial clustering in your data, locate where clustering occurs, and identify shared patterns of spatial autocorrelation between distinct variables in your data. This section gives an overview of some common measures of spatial autocorrelation and how to calculate them in R.\nMoran’s I - This is a global summary statistic of the correlation between the value of a variable in one region, and the values of the same variable in neighboring regions. The Moran’s I statistic typically ranges from -1 to 1. A value of 0 indicates no pattern of spatial correlation, while values closer to 1 or -1 indicate stronger spatial autocorrelation (similar values close together) or spatial dispersion (dissimilar values close together), respectively.\nFor an example, we will calculate a Moran’s I statistic to quantify the spatial autocorrelation in Ebola cases we mapped earlier (remember, this is a subset of cases from the simulated epidemic linelist dataframe). The spdep package has a function, moran.test, that can do this calculation for us:\n\nmoran_i <-spdep::moran.test(sle_adm3_dat$cases, # numeric vector with variable of interest\n listw = sle_listw) # listw object summarizing neighbor relationships\n\nmoran_i # print results of Moran's I test\n\n\n Moran I test under randomisation\n\ndata: sle_adm3_dat$cases \nweights: sle_listw \n\nMoran I statistic standard deviate = 1.3956, p-value = 0.08142\nalternative hypothesis: greater\nsample estimates:\nMoran I statistic Expectation Variance \n 0.16638757 -0.12500000 0.04359253 \n\n\nThe output from the moran.test() function shows us a Moran I statistic of 0.17. This indicates the presence of spatial autocorrelation in our data - specifically, that regions with similar numbers of Ebola cases are likely to be close together. The p-value provided by moran.test() is generated by comparison to the expectation under null hypothesis of no spatial autocorrelation, and can be used if you need to report the results of a formal hypothesis test.\nLocal Moran’s I - We can decompose the (global) Moran’s I statistic calculated above to identify localized spatial autocorrelation; that is, to identify specific clusters in our data. This statistic, which is sometimes called a Local Indicator of Spatial Association (LISA) statistic, summarizes the extent of spatial autocorrelation around each individual region. It can be useful for finding “hot” and “cold” spots on the map.\nTo show an example, we can calculate and map Local Moran’s I for the Ebola case counts used above, with the local_moran() function from spdep:\n\n# calculate local Moran's I\nlocal_moran <- spdep::localmoran( \n sle_adm3_dat$cases, # variable of interest\n listw = sle_listw # listw object with neighbor weights\n)\n\n# join results to sf data\nsle_adm3_dat<- cbind(sle_adm3_dat, local_moran) \n\n# plot map\nggplot(data = sle_adm3_dat) +\n geom_sf(aes(fill = Ii)) +\n theme_bw() +\n scale_fill_gradient2(low = \"#2c7bb6\", mid = \"#ffffbf\", high = \"#d7191c\",\n name = \"Local Moran's I\") +\n labs(title = \"Local Moran's I statistic for Ebola cases\",\n subtitle = \"Admin level 3 regions, Sierra Leone\")\n\n\n\n\n\n\n\n\n**Getis-Ord Gi** - This is another statistic that is commonly used for hotspot analysis; in large part, the popularity of this statistic relates to its use in the Hot Spot Analysis tool in ArcGIS. It is based on the assumption that typically, the difference in a variable’s value between neighboring regions should follow a normal distribution. It uses a z-score approach to identify regions that have significantly higher (hot spot) or significantly lower (cold spot) values of a specified variable, compared to their neighbors.\nWe can calculate and map the Gi* statistic using the localG() function from spdep:\n\n# Perform local G analysis\ngetis_ord <- spdep::localG(\n sle_adm3_dat$cases,\n sle_listw\n)\n\n# join results to sf data\nsle_adm3_dat$getis_ord <- as.numeric(getis_ord)\n\n# plot map\nggplot(data=sle_adm3_dat) +\n geom_sf(aes(fill = getis_ord)) +\n theme_bw() +\n scale_fill_gradient2(low=\"#2c7bb6\", mid = \"#ffffbf\", high = \"#d7191c\",\n name = \"Gi*\") +\n labs(title = \"Getis-Ord Gi* statistic for Ebola cases\",\n subtitle = \"Admin level 3 regions, Sierra Leone\")\n\n\n\n\n\n\n\n\nAs you can see, the map of Getis-Ord Gi looks slightly different from the map of Local Moran’s I produced earlier. This reflects that the method used to calculate these two statistics are slightly different; which one you should use depends on your specific use case and the research question of interest.\nLee’s L test - This is a statistical test for bivariate spatial correlation. It allows you to test whether the spatial pattern for a given variable x is similar to the spatial pattern of another variable, y, that is hypothesized to be related spatially to x.\nTo give an example, let’s test whether the spatial pattern of Ebola cases from the simulated epidemic is correlated with the spatial pattern of population. To start, we need to have a population variable in our sle_adm3 data. We can use the total variable from the sle_adm3_pop dataframe that we loaded earlier.\n\nsle_adm3_dat <- sle_adm3_dat %>% \n rename(population = total) # rename 'total' to 'population'\n\nWe can quickly visualize the spatial patterns of the two variables side by side, to see whether they look similar:\n\ntmap_mode(\"plot\")\n\ncases_map <- tm_shape(sle_adm3_dat) + \n tm_polygons(\"cases\") + tm_layout(main.title = \"Cases\")\npop_map <- tm_shape(sle_adm3_dat) + tm_polygons(\"population\") + \n tm_layout(main.title = \"Population\")\n\ntmap_arrange(cases_map, pop_map, ncol = 2) # arrange into 2x1 facets\n\n\n\n\n\n\n\n\nVisually, the patterns seem dissimilar. We can use the lee.test() function in spdep to test statistically whether the pattern of spatial autocorrelation in the two variables is related. The L statistic will be close to 0 if there is no correlation between the patterns, close to 1 if there is a strong positive correlation (i.e. the patterns are similar), and close to -1 if there is a strong negative correlation (i.e. the patterns are inverse).\n\nlee_test <- spdep::lee.test(\n x = sle_adm3_dat$cases, # variable 1 to compare\n y = sle_adm3_dat$population, # variable 2 to compare\n listw = sle_listw # listw object with neighbor weights\n)\n\nlee_test\n\n\n Lee's L statistic randomisation\n\ndata: sle_adm3_dat$cases , sle_adm3_dat$population \nweights: sle_listw \n\nLee's L statistic standard deviate = -0.90934, p-value = 0.8184\nalternative hypothesis: greater\nsample estimates:\nLee's L statistic Expectation Variance \n -0.14925304 -0.04823804 0.01234005 \n\n\nThe output above shows that the Lee’s L statistic for our two variables was -0.15, which indicates weak negative correlation. This confirms our visual assessment that the pattern of cases and population are not related to one another, and provides evidence that the spatial pattern of cases is not strictly a result of population density in high-risk areas.\nThe Lee L statistic can be useful for making these kinds of inferences about the relationship between spatially distributed variables; however, to describe the nature of the relationship between two variables in more detail, or adjust for confounding, spatial regression techniques will be needed. These are described briefly in the following section.\n\n\nSpatial regression\nYou may wish to make statistical inferences about the relationships between variables in your spatial data. In these cases, it is useful to consider spatial regression techniques - that is, approaches to regression that explicitly consider the spatial organization of units in your data. Some reasons that you may need to consider spatial regression models, rather than standard regression models such as GLMs, include:\n\nStandard regression models assume that residuals are independent from one another. In the presence of strong spatial autocorrelation, the residuals of a standard regression model are likely to be spatially autocorrelated as well, thus violating this assumption. This can lead to problems with interpreting the model results, in which case a spatial model would be preferred.\nRegression models also typically assume that the effect of a variable x is constant over all observations. In the case of spatial heterogeneity, the effects we wish to estimate may vary over space, and we may be interested in quantifying those differences. In this case, spatial regression models offer more flexibility for estimating and interpreting effects.\n\nThe details of spatial regression approaches are beyond the scope of this handbook. This section will instead provide an overview of the most common spatial regression models and their uses, and refer you to references that may of use if you wish to explore this area further.\nSpatial error models - These models assume that the error terms across spatial units are correlated, in which case the data would violate the assumptions of a standard OLS model. Spatial error models are also sometimes referred to as simultaneous autoregressive (SAR) models. They can be fit using the errorsarlm() function in the spatialreg package (spatial regression functions which used to be a part of spdep).\nSpatial lag models - These models assume that the dependent variable for a region i is influenced not only by value of independent variables in i, but also by the values of those variables in regions neighboring i. Like spatial error models, spatial lag models are also sometimes described as simultaneous autoregressive (SAR) models. They can be fit using the lagsarlm() function in the spatialreg package.\nThe spdep package contains several useful diagnostic tests for deciding between standard OLS, spatial lag, and spatial error models. These tests, called Lagrange Multiplier diagnostics, can be used to identify the type of spatial dependence in your data and choose which model is most appropriate. The function lm.LMtests() can be used to calculate all of the Lagrange Multiplier tests. Anselin (1988) also provides a useful flow chart tool to decide which spatial regression model to use based on the results of the Lagrange Multiplier tests:\n\n\n\n\n\n\n\n\n\nBayesian hierarchical models - Bayesian approaches are commonly used for some applications in spatial analysis, most commonly for disease mapping. They are preferred in cases where case data are sparsely distributed (for example, in the case of a rare outcome) or statistically “noisy”, as they can be used to generate “smoothed” estimates of disease risk by accounting for the underlying latent spatial process. This may improve the quality of estimates. They also allow investigator pre-specification (via choice of prior) of complex spatial correlation patterns that may exist in the data, which can account for spatially-dependent and -independent variation in both independent and dependent variables. In R, Bayesian hierarchical models can be fit using the CARbayes package (see vignette) or R-INLA (see website and textbook). R can also be used to call external software that does Bayesian estimation, such as JAGS or WinBUGS.", "crumbs": [ "Analysis", "28  GIS basics" @@ -2551,7 +2551,7 @@ "href": "new_pages/gis.html#resources", "title": "28  GIS basics", "section": "28.12 Resources", - "text": "28.12 Resources\n\nR Simple Features and sf package vignette\nR tmap package vignette\nggmap: Spatial Visualization with ggplot2\nIntro to making maps with R, overview of different packages\nSpatial Data in R (EarthLab course)\nApplied Spatial Data Analysis in R textbook\nSpatialEpiApp - a Shiny app that is downloadable as an R package, allowing you to provide your own data and conduct mapping, cluster analysis, and spatial statistics.\nAn Introduction to Spatial Econometrics in R workshop", + "text": "28.12 Resources\n\nR Simple Features and sf package vignette\nR tmap package vignette\nggmap: Spatial Visualization with ggplot2\nIntro to making maps with R, overview of different packages\nSpatial Data in R (EarthLab course)\nApplied Spatial Data Analysis in R textbook\nSpatialEpiApp - a Shiny app that is downloadable as an R package, allowing you to provide your own data and conduct mapping, cluster analysis, and spatial statistics.\n\nSpatial Statistics for Data Science: Theory and Practice with R\nGeospatial Health Data: Modeling and Visualization with R-INLA and Shiny\n\nAn Introduction to Spatial Econometrics in R workshop", "crumbs": [ "Analysis", "28  GIS basics" @@ -2573,7 +2573,7 @@ "href": "new_pages/tables_presentation.html#preparation", "title": "29  Tables for presentation", "section": "", - "text": "Load packages\nInstall and load flextable. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export\n here, # file pathways\n flextable, # make HTML tables \n officer, # helper functions for tables\n tidyverse) # data management, summary, and visualization\n\n\n\nImport data\nTo begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\nPrepare table\nBefore beginning to use flextable you will need to create your table as a data frame. See the page on Descriptive tables and Pivoting data to learn how to create a data frame using packages such as janitor and dplyr. You must arrange the content in rows and columns as you want it displayed. Then, the data frame will be passed to flextable to display it with colors, headers, fonts, etc.\nBelow is an example from the Descriptive tables page of converting the case linelist into a data frame that summarises patient outcomes and CT values by hospital, with a Totals row at the bottom. The output is saved as table.\n\ntable <- linelist %>% \n \n # Get summary values per hospital-outcome group\n ###############################################\n group_by(hospital, outcome) %>% # Group data\n summarise( # Create new summary columns of indicators of interest\n N = n(), # Number of rows per hospital-outcome group \n ct_value = median(ct_blood, na.rm=T)) %>% # median CT value per group\n \n # add totals\n ############\n bind_rows( # Bind the previous table with this mini-table of totals\n linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>%\n group_by(outcome) %>% # Grouped only by outcome, not by hospital \n summarise(\n N = n(), # Number of rows for whole dataset \n ct_value = median(ct_blood, na.rm=T))) %>% # Median CT for whole dataset\n \n # Pivot wider and format\n ########################\n mutate(hospital = replace_na(hospital, \"Total\")) %>% \n pivot_wider( # Pivot from long to wide\n values_from = c(ct_value, N), # new values are from ct and count columns\n names_from = outcome) %>% # new column names are from outcomes\n mutate( # Add new columns\n N_Known = N_Death + N_Recover, # number with known outcome\n Pct_Death = scales::percent(N_Death / N_Known, 0.1), # percent cases who died (to 1 decimal)\n Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)\n select( # Re-order columns\n hospital, N_Known, # Intro columns\n N_Recover, Pct_Recover, ct_value_Recover, # Recovered columns\n N_Death, Pct_Death, ct_value_Death) %>% # Death columns\n arrange(N_Known) # Arrange rows from lowest to highest (Total row at bottom)\n\ntable # print\n\n# A tibble: 7 × 8\n# Groups: hospital [7]\n hospital N_Known N_Recover Pct_Recover ct_value_Recover N_Death Pct_Death\n <chr> <int> <int> <chr> <dbl> <int> <chr> \n1 St. Mark's M… 325 126 38.8% 22 199 61.2% \n2 Central Hosp… 358 165 46.1% 22 193 53.9% \n3 Other 685 290 42.3% 21 395 57.7% \n4 Military Hos… 708 309 43.6% 22 399 56.4% \n5 Missing 1125 514 45.7% 21 611 54.3% \n6 Port Hospital 1364 579 42.4% 21 785 57.6% \n7 Total 3440 1469 42.7% 22 1971 57.3% \n# ℹ 1 more variable: ct_value_Death <dbl>", + "text": "Load packages\nInstall and load flextable. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export\n here, # file pathways\n flextable, # make HTML tables \n officer, # helper functions for tables\n tidyverse # data management, summary, and visualization\n ) \n\n\n\nImport data\nTo begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\nPrepare table\nBefore beginning to use flextable you will need to create your table as a data frame. See the page on Descriptive tables and Pivoting data to learn how to create a data frame using packages such as janitor and dplyr. You must arrange the content in rows and columns as you want it displayed. Then, the data frame will be passed to flextable to display it with colors, headers, fonts, etc.\nBelow is an example from the Descriptive tables page of converting the case linelist into a data frame that summarises patient outcomes and CT values by hospital, with a Totals row at the bottom. The output is saved as table.\n\ntable <- linelist %>% \n \n # Get summary values per hospital-outcome group\n ###############################################\n group_by(hospital, outcome) %>% # Group data\n summarise( # Create new summary columns of indicators of interest\n N = n(), # Number of rows per hospital-outcome group \n ct_value = median(ct_blood, na.rm=T)) %>% # median CT value per group\n \n # add totals\n ############\n bind_rows( # Bind the previous table with this mini-table of totals\n linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>%\n group_by(outcome) %>% # Grouped only by outcome, not by hospital \n summarise(\n N = n(), # Number of rows for whole dataset \n ct_value = median(ct_blood, na.rm=T))) %>% # Median CT for whole dataset\n \n # Pivot wider and format\n ########################\n mutate(hospital = replace_na(hospital, \"Total\")) %>% \n pivot_wider( # Pivot from long to wide\n values_from = c(ct_value, N), # new values are from ct and count columns\n names_from = outcome) %>% # new column names are from outcomes\n mutate( # Add new columns\n N_Known = N_Death + N_Recover, # number with known outcome\n Pct_Death = scales::percent(N_Death / N_Known, 0.1), # percent cases who died (to 1 decimal)\n Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)\n select( # Re-order columns\n hospital, N_Known, # Intro columns\n N_Recover, Pct_Recover, ct_value_Recover, # Recovered columns\n N_Death, Pct_Death, ct_value_Death) %>% # Death columns\n arrange(N_Known) # Arrange rows from lowest to highest (Total row at bottom)\n\ntable # print\n\n# A tibble: 7 × 8\n# Groups: hospital [7]\n hospital N_Known N_Recover Pct_Recover ct_value_Recover N_Death Pct_Death\n <chr> <int> <int> <chr> <dbl> <int> <chr> \n1 St. Mark's M… 325 126 38.8% 22 199 61.2% \n2 Central Hosp… 358 165 46.1% 22 193 53.9% \n3 Other 685 290 42.3% 21 395 57.7% \n4 Military Hos… 708 309 43.6% 22 399 56.4% \n5 Missing 1125 514 45.7% 21 611 54.3% \n6 Port Hospital 1364 579 42.4% 21 785 57.6% \n7 Total 3440 1469 42.7% 22 1971 57.3% \n# ℹ 1 more variable: ct_value_Death <dbl>", "crumbs": [ "Data Visualization", "29  Tables for presentation" @@ -2584,7 +2584,7 @@ "href": "new_pages/tables_presentation.html#basic-flextable", "title": "29  Tables for presentation", "section": "29.2 Basic flextable", - "text": "29.2 Basic flextable\n\nCreate a flextable\nTo create and manage flextable objects, we first pass the data frame through the flextable() function. We save the result as my_table.\n\nmy_table <- flextable(table) \nmy_table\n\nhospitalN_KnownN_RecoverPct_Recoverct_value_RecoverN_DeathPct_Deathct_value_DeathSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nAfter doing this, we can progressively pipe the my_table object through more flextable formatting functions.\nIn this page for sake of clarity we will save the table at intermediate steps as my_table, adding flextable functions bit-by-bit. If you want to see all the code from beginning to end written in one chunk, visit the All code together section below.\nThe general syntax of each line of flextable code is as follows:\n\nfunction(table, i = X, j = X, part = \"X\"), where:\n\nThe ‘function’ can be one of many different functions, such as width() to determine column widths, bg() to set background colours, align() to set whether text is centre/right/left aligned, and so on.\ntable = is the name of the data frame, although does not need to be stated if the data frame is piped into the function.\npart = refers to which part of the table the function is being applied to. E.g. “header”, “body” or “all”.\ni = specifies the row to apply the function to, where ‘X’ is the row number. If multiple rows, e.g. the first to third rows, one can specify: i = c(1:3). Note if ‘body’ is selected, the first row starts from underneath the header section.\nj = specifies the column to apply the function to, where ‘x’ is the column number or name. If multiple columns, e.g. the fifth and sixth, one can specify: j = c(5,6).\n\n\nYou can find the complete list of flextable formatting function here or review the documentation by entering ?flextable.\n\n\nColumn width\nWe can use the autofit() function, which nicely stretches out the table so that each cell only has one row of text. The function qflextable() is a convenient shorthand for flextable() and autofit().\n\nmy_table %>% autofit()\n\nhospitalN_KnownN_RecoverPct_Recoverct_value_RecoverN_DeathPct_Deathct_value_DeathSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nHowever, this might not always be appropriate, especially if there are very long values within cells, meaning the table might not fit on the page.\nInstead, we can specify widths with the width() function. It can take some playing around to know what width value to put. In the example below, we specify different widths for column 1, column 2, and columns 4 to 8.\n\nmy_table <- my_table %>% \n width(j=1, width = 2.7) %>% \n width(j=2, width = 1.5) %>% \n width(j=c(4,5,7,8), width = 1)\n\nmy_table\n\nhospitalN_KnownN_RecoverPct_Recoverct_value_RecoverN_DeathPct_Deathct_value_DeathSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nColumn headers\nWe want more clearer headers for easier interpretation of the table contents.\nFor this table, we will want to add a second header layer so that columns covering the same subgroups can be grouped together. We do this with the add_header_row() function with top = TRUE. We provide the new name of each column to values =, leaving empty values \"\" for columns we know we will merge together later.\nWe also rename the header names in the now-second header in a separate set_header_labels() command.\nFinally, to “combine” certain column headers in the top header we use merge_at() to merge the column headers in the top header row.\n\nmy_table <- my_table %>% \n \n add_header_row(\n top = TRUE, # New header goes on top of existing header row\n values = c(\"Hospital\", # Header values for each column below\n \"Total cases with known outcome\", \n \"Recovered\", # This will be the top-level header for this and two next columns\n \"\",\n \"\",\n \"Died\", # This will be the top-level header for this and two next columns\n \"\", # Leave blank, as it will be merged with \"Died\"\n \"\")) %>% \n \n set_header_labels( # Rename the columns in original header row\n hospital = \"\", \n N_Known = \"\", \n N_Recover = \"Total\",\n Pct_Recover = \"% of cases\",\n ct_value_Recover = \"Median CT values\",\n N_Death = \"Total\",\n Pct_Death = \"% of cases\",\n ct_value_Death = \"Median CT values\") %>% \n \n merge_at(i = 1, j = 3:5, part = \"header\") %>% # Horizontally merge columns 3 to 5 in new header row\n merge_at(i = 1, j = 6:8, part = \"header\") # Horizontally merge columns 6 to 8 in new header row\n\nmy_table # print\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nBorders and background\nYou can adjust the borders, internal lines, etc. with various flextable functions. It is often easier to start by removing all existing borders with border_remove().\nThen, you can apply default border themes by passing the table to theme_box(), theme_booktabs(), or theme_alafoli().\nYou can add vertical and horizontal lines with a variety of functions. hline() and vline() add lines to a specified row or column, respectively. Within each, you must specify the part = as either “all”, “body”, or “header”. For vertical lines, specify the column to j =, and for horizontal lines the row to i =. Other functions like vline_right(), vline_left(), hline_top(), and hline_bottom() add lines to the outsides only.\nIn all of these functions, the actual line style itself must be specified to border = and must be the output of a separate command using the fp_border() function from the officer package. This function helps you define the width and color of the line. You can define this above the table commands, as shown below.\n\n# define style for border line\nborder_style = officer::fp_border(color=\"black\", width=1)\n\n# add border lines to table\nmy_table <- my_table %>% \n\n # Remove all existing borders\n border_remove() %>% \n \n # add horizontal lines via a pre-determined theme setting\n theme_booktabs() %>% \n \n # add vertical lines to separate Recovered and Died sections\n vline(part = \"all\", j = 2, border = border_style) %>% # at column 2 \n vline(part = \"all\", j = 5, border = border_style) # at column 5\n\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nFont and alignment\nWe centre-align all columns aside from the left-most column with the hospital names, using the align() function from flextable.\n\nmy_table <- my_table %>% \n flextable::align(align = \"center\", j = c(2:8), part = \"all\") \nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nAdditionally, we can increase the header font size and change then to bold. We can also change the total row to bold.\n\nmy_table <- my_table %>% \n fontsize(i = 1, size = 12, part = \"header\") %>% # adjust font size of header\n bold(i = 1, bold = TRUE, part = \"header\") %>% # adjust bold face of header\n bold(i = 7, bold = TRUE, part = \"body\") # adjust bold face of total row (row 7 of body)\n\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nWe can ensure that the proportion columns display only one decimal place using the function colformat_num(). Note this could also have been done at data management stage with the round() function.\n\nmy_table <- colformat_num(my_table, j = c(4,7), digits = 1)\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nMerge cells\nJust as we merge cells horizontally in the header row, we can also merge cells vertically using merge_at() and specifying the rows (i) and column (j). Here we merge the “Hospital” and “Total cases with known outcome” values vertically to give them more space.\n\nmy_table <- my_table %>% \n merge_at(i = 1:2, j = 1, part = \"header\") %>% \n merge_at(i = 1:2, j = 2, part = \"header\")\n\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nBackground color\nTo distinguish the content of the table from the headers, we may want to add additional formatting. e.g. changing the background color. In this example we change the table body to gray.\n\nmy_table <- my_table %>% \n bg(part = \"body\", bg = \"gray95\") \n\nmy_table \n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22", + "text": "29.2 Basic flextable\n\nCreate a flextable\nTo create and manage flextable objects, we first pass the data frame through the flextable() function. We save the result as my_table.\n\nmy_table <- flextable(table) \nmy_table\n\nhospitalN_KnownN_RecoverPct_Recoverct_value_RecoverN_DeathPct_Deathct_value_DeathSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nAfter doing this, we can progressively pipe the my_table object through more flextable formatting functions.\nIn this page for sake of clarity we will save the table at intermediate steps as my_table, adding flextable functions bit-by-bit. If you want to see all the code from beginning to end written in one chunk, visit the All code together section below.\nThe general syntax of each line of flextable code is as follows:\n\nfunction(table, i = X, j = X, part = \"X\"), where:\n\nThe ‘function’ can be one of many different functions, such as width() to determine column widths, bg() to set background colours, align() to set whether text is centre/right/left aligned, and so on.\ntable = is the name of the data frame, although does not need to be stated if the data frame is piped into the function.\npart = refers to which part of the table the function is being applied to. E.g. “header”, “body” or “all”.\ni = specifies the row to apply the function to, where ‘X’ is the row number. If multiple rows, e.g. the first to third rows, one can specify: i = c(1:3). Note if ‘body’ is selected, the first row starts from underneath the header section.\nj = specifies the column to apply the function to, where ‘x’ is the column number or name. If multiple columns, e.g. the fifth and sixth, one can specify: j = c(5,6).\n\n\nYou can find the complete list of flextable formatting function here or review the documentation by entering ?flextable.\n\n\nColumn width\nWe can use the autofit() function, which nicely stretches out the table so that each cell only has one row of text. The function qflextable() is a convenient shorthand for flextable() and autofit().\n\nmy_table %>% autofit()\n\nhospitalN_KnownN_RecoverPct_Recoverct_value_RecoverN_DeathPct_Deathct_value_DeathSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nHowever, this might not always be appropriate, especially if there are very long values within cells, meaning the table might not fit on the page.\nInstead, we can specify widths with the width() function. It can take some playing around to know what width value to put. In the example below, we specify different widths for column 1, column 2, and columns 4 to 8.\n\nmy_table <- my_table %>% \n width(j=1, width = 2.7) %>% \n width(j=2, width = 1.5) %>% \n width(j=c(4,5,7,8), width = 1)\n\nmy_table\n\nhospitalN_KnownN_RecoverPct_Recoverct_value_RecoverN_DeathPct_Deathct_value_DeathSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nColumn headers\nWe want more clearer headers for easier interpretation of the table contents.\nFor this table, we will want to add a second header layer so that columns covering the same subgroups can be grouped together. We do this with the add_header_row() function with top = TRUE. We provide the new name of each column to values =, leaving empty values \"\" for columns we know we will merge together later.\nWe also rename the header names in the now-second header in a separate set_header_labels() command.\nFinally, to “combine” certain column headers in the top header we use merge_at() to merge the column headers in the top header row.\n\nmy_table <- my_table %>% \n \n add_header_row(\n top = TRUE, # New header goes on top of existing header row\n values = c(\"Hospital\", # Header values for each column below\n \"Total cases with known outcome\", \n \"Recovered\", # This will be the top-level header for this and two next columns\n \"\",\n \"\",\n \"Died\", # This will be the top-level header for this and two next columns\n \"\", # Leave blank, as it will be merged with \"Died\"\n \"\")) %>% \n \n set_header_labels( # Rename the columns in original header row\n hospital = \"\", \n N_Known = \"\", \n N_Recover = \"Total\",\n Pct_Recover = \"% of cases\",\n ct_value_Recover = \"Median CT values\",\n N_Death = \"Total\",\n Pct_Death = \"% of cases\",\n ct_value_Death = \"Median CT values\") %>% \n \n merge_at(i = 1, j = 3:5, part = \"header\") %>% # Horizontally merge columns 3 to 5 in new header row\n merge_at(i = 1, j = 6:8, part = \"header\") # Horizontally merge columns 6 to 8 in new header row\n\nmy_table # print\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nBorders and background\nYou can adjust the borders, internal lines, etc., with various flextable functions. It is often easier to start by removing all existing borders with border_remove().\nThen, you can apply default border themes by passing the table to theme_box(), theme_booktabs(), or theme_alafoli().\nYou can add vertical and horizontal lines with a variety of functions. hline() and vline() add lines to a specified row or column, respectively. Within each, you must specify the part = as either “all”, “body”, or “header”. For vertical lines, specify the column to j =, and for horizontal lines the row to i =. Other functions like vline_right(), vline_left(), hline_top(), and hline_bottom() add lines to the outsides only.\nIn all of these functions, the actual line style itself must be specified to border = and must be the output of a separate command using the fp_border() function from the officer package. This function helps you define the width and color of the line. You can define this above the table commands, as shown below.\n\n# define style for border line\nborder_style = officer::fp_border(color=\"black\", width=1)\n\n# add border lines to table\nmy_table <- my_table %>% \n\n # Remove all existing borders\n border_remove() %>% \n \n # add horizontal lines via a pre-determined theme setting\n theme_booktabs() %>% \n \n # add vertical lines to separate Recovered and Died sections\n vline(part = \"all\", j = 2, border = border_style) %>% # at column 2 \n vline(part = \"all\", j = 5, border = border_style) # at column 5\n\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nFont and alignment\nWe centre-align all columns aside from the left-most column with the hospital names, using the align() function from flextable.\n\nmy_table <- my_table %>% \n flextable::align(align = \"center\", j = c(2:8), part = \"all\") \nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nAdditionally, we can increase the header font size and change then to bold. We can also change the total row to bold.\n\nmy_table <- my_table %>% \n fontsize(i = 1, size = 12, part = \"header\") %>% # adjust font size of header\n bold(i = 1, bold = TRUE, part = \"header\") %>% # adjust bold face of header\n bold(i = 7, bold = TRUE, part = \"body\") # adjust bold face of total row (row 7 of body)\n\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nWe can ensure that the proportion columns display only one decimal place using the function colformat_num(). Note this could also have been done at data management stage with the round() function.\n\nmy_table <- colformat_num(my_table, j = c(4, 7), digits = 1)\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nMerge cells\nJust as we merge cells horizontally in the header row, we can also merge cells vertically using merge_at() and specifying the rows (i) and column (j). Here we merge the “Hospital” and “Total cases with known outcome” values vertically to give them more space.\n\nmy_table <- my_table %>% \n merge_at(i = 1:2, j = 1, part = \"header\") %>% \n merge_at(i = 1:2, j = 2, part = \"header\")\n\nmy_table\n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\n\n\nBackground color\nTo distinguish the content of the table from the headers, we may want to add additional formatting. e.g. changing the background color. In this example we change the table body to gray.\n\nmy_table <- my_table %>% \n bg(part = \"body\", bg = \"gray95\") \n\nmy_table \n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22", "crumbs": [ "Data Visualization", "29  Tables for presentation" @@ -2595,7 +2595,7 @@ "href": "new_pages/tables_presentation.html#conditional-formatting", "title": "29  Tables for presentation", "section": "29.3 Conditional formatting", - "text": "29.3 Conditional formatting\nWe can highlight all values in a column that meet a certain rule, e.g. where more than 55% of cases died. Simply put the criteria to the i = or j = argument, preceded by a tilde ~. Reference the column in the data frame, not the display heading values.\n\nmy_table %>% \n bg(j = 7, i = ~ Pct_Death >= 55, part = \"body\", bg = \"red\") \n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nOr, we can highlight the entire row meeting a certain criterion, such as a hospital of interest. To do this we just remove the column (j) specification so the criteria apply to all columns.\n\nmy_table %>% \n bg(., i= ~ hospital == \"Military Hospital\", part = \"body\", bg = \"#91c293\") \n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22", + "text": "29.3 Conditional formatting\nWe can highlight all values in a column that meet a certain rule, e.g. where more than 55% of cases died. Simply put the criteria to the i = or j = argument, preceded by a tilde ~. Reference the column in the data frame, not the display heading values.\n\nmy_table %>% \n bg(j = 7, i = ~ Pct_Death > 55, part = \"body\", bg = \"red\") \n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22\n\n\nOr, we can highlight the entire row meeting a certain criterion, such as a hospital of interest. To do this we just remove the column (j) specification so the criteria apply to all columns.\n\nmy_table %>% \n bg(., i= ~ hospital == \"Military Hospital\", part = \"body\", bg = \"#91c293\") \n\nHospitalTotal cases with known outcomeRecoveredDiedTotal% of casesMedian CT valuesTotal% of casesMedian CT valuesSt. Mark's Maternity Hospital (SMMH)32512638.8%2219961.2%22Central Hospital35816546.1%2219353.9%22Other68529042.3%2139557.7%22Military Hospital70830943.6%2239956.4%21Missing1,12551445.7%2161154.3%21Port Hospital1,36457942.4%2178557.6%22Total3,4401,46942.7%221,97157.3%22", "crumbs": [ "Data Visualization", "29  Tables for presentation" @@ -2628,7 +2628,7 @@ "href": "new_pages/tables_presentation.html#resources", "title": "29  Tables for presentation", "section": "29.6 Resources", - "text": "29.6 Resources\nThe full flextable book is here: https://ardata-fr.github.io/flextable-book/ The Github site is here\nA manual of all the flextable functions can be found here\nA gallery of beautiful example flextable tables with code can be accessed here", + "text": "29.6 Resources\nThe full flextable book is here.\nThe Github site is here.\nA manual of all the flextable functions can be found here.\nA gallery of beautiful example flextable tables with code can be accessed here.", "crumbs": [ "Data Visualization", "29  Tables for presentation" @@ -2650,7 +2650,7 @@ "href": "new_pages/ggplot_basics.html#preparation", "title": "30  ggplot basics", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # includes ggplot2 and other data management tools\n janitor, # cleaning and summary tables\n ggforce, # ggplot extras\n rio, # import/export\n here, # file locator\n stringr # working with characters \n)\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\nlinelist <- rio::import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below. We will focus on the continuous variables age, wt_kg (weight in kilos), ct_blood (CT values), and days_onset_hosp (difference between onset date and hospitalisation).\n\n\n\n\n\n\n\n\nGeneral cleaning\nWhen preparing data to plot, it is best to make the data adhere to “tidy” data standards as much as possible. How to achieve this is expanded on in the data management pages of this handbook, such as Cleaning data and core functions.\nSome simple ways we can prepare our data to make it better for plotting can include making the contents of the data better for display - which does not necessarily equate to better for data manipulation. For example:\n\nReplace NA values in a character column with the character string “Unknown”\n\nConsider converting column to class factor so their values have prescribed ordinal levels\n\nClean some columns so that their “data friendly” values with underscores etc are changed to normal text or title case (see Characters and strings)\n\nHere are some examples of this in action:\n\n# make display version of columns with more friendly names\nlinelist <- linelist %>%\n mutate(\n gender_disp = case_when(gender == \"m\" ~ \"Male\", # m to Male \n gender == \"f\" ~ \"Female\", # f to Female,\n is.na(gender) ~ \"Unknown\"), # NA to Unknown\n \n outcome_disp = replace_na(outcome, \"Unknown\") # replace NA outcome with \"unknown\"\n )\n\n\n\nPivoting longer\nAs a matter of data structure, for ggplot2 we often also want to pivot our data into longer formats. Read more about this is the page on Pivoting data.\n\n\n\n\n\n\n\n\n\nFor example, say that we want to plot data that are in a “wide” format, such as for each case in the linelist and their symptoms. Below we create a mini-linelist called symptoms_data that contains only the case_id and symptoms columns.\n\nsymptoms_data <- linelist %>% \n select(c(case_id, fever, chills, cough, aches, vomit))\n\nHere is how the first 50 rows of this mini-linelist look - see how they are formatted “wide” with each symptom as a column:\n\n\n\n\n\n\nIf we wanted to plot the number of cases with specific symptoms, we are limited by the fact that each symptom is a specific column. However, we can pivot the symptoms columns to a longer format like this:\n\nsymptoms_data_long <- symptoms_data %>% # begin with \"mini\" linelist called symptoms_data\n \n pivot_longer(\n cols = -case_id, # pivot all columns except case_id (all the symptoms columns)\n names_to = \"symptom_name\", # assign name for new column that holds the symptoms\n values_to = \"symptom_is_present\") %>% # assign name for new column that holds the values (yes/no)\n \n mutate(symptom_is_present = replace_na(symptom_is_present, \"unknown\")) # convert NA to \"unknown\"\n\nHere are the first 50 rows. Note that case has 5 rows - one for each possible symptom. The new columns symptom_name and symptom_is_present are the result of the pivot. Note that this format may not be very useful for other operations, but is useful for plotting.", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # includes ggplot2 and other data management tools\n janitor, # cleaning and summary tables\n ggforce, # ggplot extras\n rio, # import/export\n here, # file locator\n stringr # working with characters \n)\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\nlinelist <- rio::import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below. We will focus on the continuous variables age, wt_kg (weight in kilos), ct_blood (CT values), and days_onset_hosp (difference between onset date and hospitalisation).\n\n\n\n\n\n\n\n\nGeneral cleaning\nWhen preparing data to plot, it is best to make the data adhere to “tidy” data standards as much as possible. How to achieve this is expanded on in the data management pages of this handbook, such as Cleaning data and core functions.\nSome simple ways we can prepare our data to make it better for plotting can include making the contents of the data better for display - which does not necessarily equate to better for data manipulation. For example:\n\nReplace NA values in a character column with the character string “Unknown”.\n\nConsider converting column to class factor so their values have prescribed ordinal levels.\n\nClean some columns so that their “data friendly” values with underscores etc are changed to normal text or title case (see Characters and strings).\n\nHere are some examples of this in action:\n\n# make display version of columns with more friendly names\nlinelist <- linelist %>%\n mutate(\n gender_disp = case_when(gender == \"m\" ~ \"Male\", # m to Male \n gender == \"f\" ~ \"Female\", # f to Female,\n is.na(gender) ~ \"Unknown\"), # NA to Unknown\n \n outcome_disp = replace_na(outcome, \"Unknown\") # replace NA outcome with \"unknown\"\n )\n\n\n\nPivoting longer\nAs a matter of data structure, for ggplot2 we often also want to pivot our data into longer formats. Read more about this is the page on Pivoting data.\n\n\n\n\n\n\n\n\n\nFor example, say that we want to plot data that are in a “wide” format, such as for each case in the linelist and their symptoms. Below we create a mini-linelist called symptoms_data that contains only the case_id and symptoms columns.\n\nsymptoms_data <- linelist %>% \n select(c(case_id, fever, chills, cough, aches, vomit))\n\nHere is how the first 50 rows of this mini-linelist look - see how they are formatted “wide” with each symptom as a column:\n\n\n\n\n\n\nIf we wanted to plot the number of cases with specific symptoms, we are limited by the fact that each symptom is a specific column. However, we can pivot the symptoms columns to a longer format like this:\n\nsymptoms_data_long <- symptoms_data %>% # begin with \"mini\" linelist called symptoms_data\n \n pivot_longer(\n cols = -case_id, # pivot all columns except case_id (all the symptoms columns)\n names_to = \"symptom_name\", # assign name for new column that holds the symptoms\n values_to = \"symptom_is_present\") %>% # assign name for new column that holds the values (yes/no)\n \n mutate(symptom_is_present = replace_na(symptom_is_present, \"unknown\")) # convert NA to \"unknown\"\n\nHere are the first 50 rows. Note that case has 5 rows - one for each possible symptom. The new columns symptom_name and symptom_is_present are the result of the pivot. Note that this format may not be very useful for other operations, but is useful for plotting.", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2661,7 +2661,7 @@ "href": "new_pages/ggplot_basics.html#basics-of-ggplot", "title": "30  ggplot basics", "section": "30.2 Basics of ggplot", - "text": "30.2 Basics of ggplot\n“Grammar of graphics” - ggplot2\nPlotting with ggplot2 is based on “adding” plot layers and design elements on top of one another, with each command added to the previous ones with a plus symbol (+). The result is a multi-layer plot object that can be saved, modified, printed, exported, etc.\nggplot objects can be highly complex, but the basic order of layers will usually look like this:\n\nBegin with the baseline ggplot() command - this “opens” the ggplot and allow subsequent functions to be added with +. Typically the dataset is also specified in this command\n\nAdd “geom” layers - these functions visualize the data as geometries (shapes), e.g. as a bar graph, line plot, scatter plot, histogram (or a combination!). These functions all start with geom_ as a prefix.\n\nAdd design elements to the plot such as axis labels, title, fonts, sizes, color schemes, legends, or axes rotation\n\nA simple example of skeleton code is as follows. We will explain each component in the sections below.\n\n# plot data from my_data columns as red points\nggplot(data = my_data)+ # use the dataset \"my_data\"\n geom_point( # add a layer of points (dots)\n mapping = aes(x = col1, y = col2), # \"map\" data column to axes\n color = \"red\")+ # other specification for the geom\n labs()+ # here you add titles, axes labels, etc.\n theme() # here you adjust color, font, size etc of non-data plot elements (axes, title, etc.)", + "text": "30.2 Basics of ggplot\n“Grammar of graphics” - ggplot2\nPlotting with ggplot2 is based on “adding” plot layers and design elements on top of one another, with each command added to the previous ones with a plus symbol (+). The result is a multi-layer plot object that can be saved, modified, printed, exported, etc.\nggplot objects can be highly complex, but the basic order of layers will usually look like this:\n\nBegin with the baseline ggplot() command - this “opens” the ggplot and allow subsequent functions to be added with +. Typically the dataset is also specified in this command.\n\nAdd “geom” layers - these functions visualize the data as geometries (shapes), e.g. as a bar graph, line plot, scatter plot, histogram (or a combination!). These functions all start with geom_ as a prefix.\n\nAdd design elements to the plot such as axis labels, title, fonts, sizes, color schemes, legends, or axes rotation.\n\nA simple example of skeleton code is as follows. We will explain each component in the sections below.\n\n# plot data from my_data columns as red points\nggplot(data = my_data) + # use the dataset \"my_data\"\n geom_point( # add a layer of points (dots)\n mapping = aes(x = col1, y = col2), # \"map\" data column to axes\n color = \"red\") + # other specification for the geom\n labs() + # here you add titles, axes labels, etc.\n theme() # here you adjust color, font, size etc of non-data plot elements (axes, title, etc.)", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2694,7 +2694,7 @@ "href": "new_pages/ggplot_basics.html#ggplot_basics_mapping", "title": "30  ggplot basics", "section": "30.5 Mapping data to the plot", - "text": "30.5 Mapping data to the plot\nMost geom functions must be told what to use to create their shapes - so you must tell them how they should map (assign) columns in your data to components of the plot like the axes, shape colors, shape sizes, etc. For most geoms, the essential components that must be mapped to columns in the data are the x-axis, and (if necessary) the y-axis.\nThis “mapping” occurs with the mapping = argument. The mappings you provide to mapping must be wrapped in the aes() function, so you would write something like mapping = aes(x = col1, y = col2), as shown below.\nBelow, in the ggplot() command the data are set as the case linelist. In the mapping = aes() argument the column age is mapped to the x-axis, and the column wt_kg is mapped to the y-axis.\nAfter a +, the plotting commands continue. A shape is created with the “geom” function geom_point(). This geom inherits the mappings from the ggplot() command above - it knows the axis-column assignments and proceeds to visualize those relationships as points on the canvas.\n\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+\n geom_point()\n\n\n\n\n\n\n\n\nAs another example, the following commands utilize the same data, a slightly different mapping, and a different geom. The geom_histogram() function only requires a column mapped to the x-axis, as the counts y-axis is generated automatically.\n\nggplot(data = linelist, mapping = aes(x = age))+\n geom_histogram()\n\n\n\n\n\n\n\n\n\nPlot aesthetics\nIn ggplot terminology a plot “aesthetic” has a specific meaning. It refers to a visual property of plotted data. Note that “aesthetic” here refers to the data being plotted in geoms/shapes - not the surrounding display such as titles, axis labels, background color, that you might associate with the word “aesthetics” in common English. In ggplot those details are called “themes” and are adjusted within a theme() command (see this section).\nTherefore, plot object aesthetics can be colors, sizes, transparencies, placement, etc. of the plotted data. Not all geoms will have the same aesthetic options, but many can be used by most geoms. Here are some examples:\n\nshape = Display a point with geom_point() as a dot, star, triangle, or square…\n\nfill = The interior color (e.g. of a bar or boxplot)\n\ncolor = The exterior line of a bar, boxplot, etc., or the point color if using geom_point()\n\nsize = Size (e.g. line thickness, point size)\n\nalpha = Transparency (1 = opaque, 0 = invisible)\n\nbinwidth = Width of histogram bins\n\nwidth = Width of “bar plot” columns\n\nlinetype = Line type (e.g. solid, dashed, dotted)\n\nThese plot object aesthetics can be assigned values in two ways:\n\nAssigned a static value (e.g. color = \"blue\") to apply across all plotted observations\n\nAssigned to a column of the data (e.g. color = hospital) such that display of each observation depends on its value in that column\n\n\n\n\nSet to a static value\nIf you want the plot object aesthetic to be static, that is - to be the same for every observation in the data, you write its assignment within the geom but outside of any mapping = aes() statement. These assignments could look like size = 1 or color = \"blue\". Here are two examples:\n\nIn the first example, the mapping = aes() is in the ggplot() command and the axes are mapped to age and weight columns in the data. The plot aesthetics color =, size =, and alpha = (transparency) are assigned to static values. For clarity, this is done in the geom_point() function, as you may add other geoms afterward that would take different values for their plot aesthetics.\n\nIn the second example, the histogram requires only the x-axis mapped to a column. The histogram binwidth =, color =, fill = (internal color), and alpha = are again set within the geom to static values.\n\n\n# scatterplot\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ # set data and axes mapping\n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2) # set static point aesthetics\n\n# histogram\nggplot(data = linelist, mapping = aes(x = age))+ # set data and axes\n geom_histogram( # display histogram\n binwidth = 7, # width of bins\n color = \"red\", # bin line color\n fill = \"blue\", # bin interior color\n alpha = 0.1) # bin transparency\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nScaled to column values\nThe alternative is to scale the plot object aesthetic by the values in a column. In this approach, the display of this aesthetic will depend on that observation’s value in that column of the data. If the column values are continuous, the display scale (legend) for that aesthetic will be continuous. If the column values are discrete, the legend will display each value and the plotted data will appear as distinctly “grouped” (read more in the grouping section of this page).\nTo achieve this, you map that plot aesthetic to a column name (not in quotes). This must be done within a mapping = aes() function (note: there are several places in the code you can make these mapping assignments, as discussed below).\nTwo examples are below.\n\nIn the first example, the color = aesthetic (of each point) is mapped to the column age - and a scale has appeared in a legend! For now just note that the scale exists - we will show how to modify it in later sections.\n\nIn the second example two new plot aesthetics are also mapped to columns (color = and size =), while the plot aesthetics shape = and alpha = are mapped to static values outside of any mapping = aes() function.\n\n\n# scatterplot\nggplot(data = linelist, # set data\n mapping = aes( # map aesthetics to column values\n x = age, # map x-axis to age \n y = wt_kg, # map y-axis to weight\n color = age)\n )+ # map color to age\n geom_point() # display data as points \n\n# scatterplot\nggplot(data = linelist, # set data\n mapping = aes( # map aesthetics to column values\n x = age, # map x-axis to age \n y = wt_kg, # map y-axis to weight\n color = age, # map color to age\n size = age))+ # map size to age\n geom_point( # display data as points\n shape = \"diamond\", # points display as diamonds\n alpha = 0.3) # point transparency at 30%\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNote: Axes assignments are always assigned to columns in the data (not to static values), and this is always done within mapping = aes().\nIt becomes important to keep track of your plot layers and aesthetics when making more complex plots - for example plots with multiple geoms. In the example below, the size = aesthetic is assigned twice - once for geom_point() and once for geom_smooth() - both times as a static value.\n\nggplot(data = linelist,\n mapping = aes( # map aesthetics to columns\n x = age,\n y = wt_kg,\n color = age_years)\n ) + \n geom_point( # add points for each row of data\n size = 1,\n alpha = 0.5) + \n geom_smooth( # add a trend line \n method = \"lm\", # with linear method\n size = 2) # size (width of line) of 2\n\n\n\n\n\n\n\n\n\n\nWhere to make mapping assignments\nAesthetic mapping within mapping = aes() can be written in several places in your plotting commands and can even be written more than once. This can be written in the top ggplot() command, and/or for each individual geom beneath. The nuances include:\n\nMapping assignments made in the top ggplot() command will be inherited as defaults across any geom below, like how x = and y = are inherited\nMapping assignments made within one geom apply only to that geom\n\nLikewise, data = specified in the top ggplot() will apply by default to any geom below, but you could also specify data for each geom (but this is more difficult).\nThus, each of the following commands will create the same plot:\n\n# These commands will produce the exact same plot\nggplot(data = linelist, mapping = aes(x = age))+\n geom_histogram()\n\nggplot(data = linelist)+\n geom_histogram(mapping = aes(x = age))\n\nggplot()+\n geom_histogram(data = linelist, mapping = aes(x = age))\n\n\n\nGroups\nYou can easily group the data and “plot by group”. In fact, you have already done this!\nAssign the “grouping” column to the appropriate plot aesthetic, within a mapping = aes(). Above, we demonstrated this using continuous values when we assigned point size = to the column age. However this works the same way for discrete/categorical columns.\nFor example, if you want points to be displayed by gender, you would set mapping = aes(color = gender). A legend automatically appears. This assignment can be made within the mapping = aes() in the top ggplot() command (and be inherited by the geom), or it could be set in a separate mapping = aes() within the geom. Both approaches are shown below:\n\nggplot(data = linelist,\n mapping = aes(x = age, y = wt_kg, color = gender))+\n geom_point(alpha = 0.5)\n\n\n\n\n\n\n\n\n\n# This alternative code produces the same plot\nggplot(data = linelist,\n mapping = aes(x = age, y = wt_kg))+\n geom_point(\n mapping = aes(color = gender),\n alpha = 0.5)\n\nNote that depending on the geom, you will need to use different arguments to group the data. For geom_point() you will most likely use color =, shape = or size =. Whereas for geom_bar() you are more likely to use fill =. This just depends on the geom and what plot aesthetic you want to reflect the groupings.\nFor your information - the most basic way of grouping the data is by using only the group = argument within mapping = aes(). However, this by itself will not change the colors, fill, or shapes. Nor will it create a legend. Yet the data are grouped, so statistical displays may be affected.\nTo adjust the order of groups in a plot, see the ggplot tips page or the page on Factors. There are many examples of grouped plots in the sections below on plotting continuous and categorical data.", + "text": "30.5 Mapping data to the plot\nMost geom functions must be told what to use to create their shapes - so you must tell them how they should map (assign) columns in your data to components of the plot like the axes, shape colors, shape sizes, etc. For most geoms, the essential components that must be mapped to columns in the data are the x-axis, and (if necessary) the y-axis.\nThis “mapping” occurs with the mapping = argument. The mappings you provide to mapping must be wrapped in the aes() function, so you would write something like mapping = aes(x = col1, y = col2), as shown below.\nBelow, in the ggplot() command the data are set as the case linelist. In the mapping = aes() argument the column age is mapped to the x-axis, and the column wt_kg is mapped to the y-axis.\nAfter a +, the plotting commands continue. A shape is created with the “geom” function geom_point(). This geom inherits the mappings from the ggplot() command above - it knows the axis-column assignments and proceeds to visualize those relationships as points on the canvas.\n\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) +\n geom_point()\n\n\n\n\n\n\n\n\nAs another example, the following commands utilize the same data, a slightly different mapping, and a different geom. The geom_histogram() function only requires a column mapped to the x-axis, as the counts y-axis is generated automatically.\n\nggplot(data = linelist, \n mapping = aes(x = age)) +\n geom_histogram()\n\n\n\n\n\n\n\n\n\nPlot aesthetics\nIn ggplot terminology a plot “aesthetic” has a specific meaning. It refers to a visual property of plotted data. Note that “aesthetic” here refers to the data being plotted in geoms/shapes - not the surrounding display such as titles, axis labels, background color, that you might associate with the word “aesthetics” in common English. In ggplot those details are called “themes” and are adjusted within a theme() command (see this section).\nTherefore, plot object aesthetics can be colors, sizes, transparencies, placement, etc. of the plotted data. Not all geoms will have the same aesthetic options, but many can be used by most geoms. Here are some examples:\n\nshape = Display a point with geom_point() as a dot, star, triangle, or square, etc.\n\nfill = The interior color (e.g. of a bar or boxplot).\n\ncolor = The exterior line of a bar, boxplot, etc., or the point color if using geom_point().\n\nsize = Size (e.g. line thickness, point size).\n\nalpha = Transparency (1 = opaque, 0 = invisible).\n\nbinwidth = Width of histogram bins.\n\nwidth = Width of “bar plot” columns.\n\nlinetype = Line type (e.g. solid, dashed, dotted).\n\nThese plot object aesthetics can be assigned values in two ways:\n\nAssigned a static value (e.g. color = \"blue\") to apply across all plotted observations.\n\nAssigned to a column of the data (e.g. color = hospital) such that display of each observation depends on its value in that column.\n\n\n\n\nSet to a static value\nIf you want the plot object aesthetic to be static, that is - to be the same for every observation in the data, you write its assignment within the geom but outside of any mapping = aes() statement. These assignments could look like size = 1 or color = \"blue\". Here are two examples:\n\nIn the first example, the mapping = aes() is in the ggplot() command and the axes are mapped to age and weight columns in the data. The plot aesthetics color =, size =, and alpha = (transparency) are assigned to static values. For clarity, this is done in the geom_point() function, as you may add other geoms afterward that would take different values for their plot aesthetics.\n\nIn the second example, the histogram requires only the x-axis mapped to a column. The histogram binwidth =, color =, fill = (internal color), and alpha = are again set within the geom to static values.\n\n\n# scatterplot\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + # set data and axes mapping\n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2) # set static point aesthetics\n\n# histogram\nggplot(data = linelist, \n mapping = aes(x = age)) + # set data and axes\n geom_histogram( # display histogram\n binwidth = 7, # width of bins\n color = \"red\", # bin line color\n fill = \"blue\", # bin interior color\n alpha = 0.1) # bin transparency\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nScaled to column values\nThe alternative is to scale the plot object aesthetic by the values in a column. In this approach, the display of this aesthetic will depend on that observation’s value in that column of the data. If the column values are continuous, the display scale (legend) for that aesthetic will be continuous. If the column values are discrete, the legend will display each value and the plotted data will appear as distinctly “grouped” (read more in the grouping section of this page).\nTo achieve this, you map that plot aesthetic to a column name (not in quotes). This must be done within a mapping = aes() function (note: there are several places in the code you can make these mapping assignments, as discussed below.\nTwo examples are below.\n\nIn the first example, the color = aesthetic (of each point) is mapped to the column age - and a scale has appeared in a legend! For now just note that the scale exists - we will show how to modify it in later sections.\n\nIn the second example two new plot aesthetics are also mapped to columns (color = and size =), while the plot aesthetics shape = and alpha = are mapped to static values outside of any mapping = aes() function.\n\n\n# scatterplot\nggplot(data = linelist, # set data\n mapping = aes( # map aesthetics to column values\n x = age, # map x-axis to age \n y = wt_kg, # map y-axis to weight\n color = age)\n ) + # map color to age\n geom_point() # display data as points \n\n# scatterplot\nggplot(data = linelist, # set data\n mapping = aes( # map aesthetics to column values\n x = age, # map x-axis to age \n y = wt_kg, # map y-axis to weight\n color = age, # map color to age\n size = age)) + # map size to age\n geom_point( # display data as points\n shape = \"diamond\", # points display as diamonds\n alpha = 0.3) # point transparency at 30%\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNote: Axes assignments are always assigned to columns in the data (not to static values), and this is always done within mapping = aes().\nIt becomes important to keep track of your plot layers and aesthetics when making more complex plots - for example plots with multiple geoms. In the example below, the size = aesthetic is assigned twice - once for geom_point() and once for geom_smooth() - both times as a static value.\n\nggplot(data = linelist,\n mapping = aes( # map aesthetics to columns\n x = age,\n y = wt_kg,\n color = age_years)\n ) + \n geom_point( # add points for each row of data\n size = 1,\n alpha = 0.5) + \n geom_smooth( # add a trend line \n method = \"lm\", # with linear method\n size = 2) # size (width of line) of 2\n\n\n\n\n\n\n\n\n\n\nWhere to make mapping assignments\nAesthetic mapping within mapping = aes() can be written in several places in your plotting commands and can even be written more than once. This can be written in the top ggplot() command, and/or for each individual geom beneath. The nuances include:\n\nMapping assignments made in the top ggplot() command will be inherited as defaults across any geom below, like how x = and y = are inherited.\nMapping assignments made within one geom apply only to that geom.\n\nLikewise, data = specified in the top ggplot() will apply by default to any geom below, but you could also specify data for each geom (but this is more difficult).\nThus, each of the following commands will create the same plot:\n\n# These commands will produce the exact same plot\nggplot(data = linelist, \n mapping = aes(x = age)) +\n geom_histogram()\n\nggplot(data = linelist) +\n geom_histogram(mapping = aes(x = age))\n\nggplot() +\n geom_histogram(data = linelist, mapping = aes(x = age))\n\n\n\nGroups\nYou can easily group the data and “plot by group”. In fact, you have already done this!\nAssign the “grouping” column to the appropriate plot aesthetic, within a mapping = aes(). Above, we demonstrated this using continuous values when we assigned point size = to the column age. However this works the same way for discrete/categorical columns.\nFor example, if you want points to be displayed by gender, you would set mapping = aes(color = gender). A legend automatically appears. This assignment can be made within the mapping = aes() in the top ggplot() command (and be inherited by the geom), or it could be set in a separate mapping = aes() within the geom. Both approaches are shown below:\n\nggplot(data = linelist,\n mapping = aes(x = age, y = wt_kg, color = gender)) +\n geom_point(alpha = 0.5)\n\n\n\n\n\n\n\n\n\n# This alternative code produces the same plot\nggplot(data = linelist,\n mapping = aes(x = age, y = wt_kg)) +\n geom_point(\n mapping = aes(color = gender),\n alpha = 0.5)\n\nNote that depending on the geom, you will need to use different arguments to group the data. For geom_point() you will most likely use color =, shape = or size =. Whereas for geom_bar() you are more likely to use fill =. This just depends on the geom and what plot aesthetic you want to reflect the groupings.\nFor your information - the most basic way of grouping the data is by using only the group = argument within mapping = aes(). However, this by itself will not change the colors, fill, or shapes. Nor will it create a legend. Yet the data are grouped, so statistical displays may be affected.\nTo adjust the order of groups in a plot, see the ggplot tips page or the page on Factors. There are many examples of grouped plots in the sections below on plotting continuous and categorical data.", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2705,7 +2705,7 @@ "href": "new_pages/ggplot_basics.html#ggplot_basics_facet", "title": "30  ggplot basics", "section": "30.6 Facets / Small-multiples", - "text": "30.6 Facets / Small-multiples\nFacets, or “small-multiples”, are used to split one plot into a multi-panel figure, with one panel (“facet”) per group of data. The same type of plot is created multiple times, each one using a sub-group of the same dataset.\nFaceting is a functionality that comes with ggplot2, so the legends and axes of the facet “panels” are automatically aligned. There are other packages discussed in the ggplot tips page that are used to combine completely different plots (cowplot and patchwork) into one figure.\nFaceting is done with one of the following ggplot2 functions:\n\nfacet_wrap() To show a different panel for each level of a single variable. One example of this could be showing a different epidemic curve for each hospital in a region. Facets are ordered alphabetically, unless the variable is a factor with other ordering defined.\n\n\n\nYou can invoke certain options to determine the layout of the facets, e.g. nrow = 1 or ncol = 1 to control the number of rows or columns that the faceted plots are arranged within.\n\n\nfacet_grid() This is used when you want to bring a second variable into the faceting arrangement. Here each panel of a grid shows the intersection between values in two columns. For example, epidemic curves for each hospital-age group combination with hospitals along the top (columns) and age groups along the sides (rows).\n\n\n\nnrow and ncol are not relevant, as the subgroups are presented in a grid\n\nEach of these functions accept a formula syntax to specify the column(s) for faceting. Both accept up to two columns, one on each side of a tilde ~.\n\nFor facet_wrap() most often you will write only one column preceded by a tilde ~ like facet_wrap(~hospital). However you can write two columns facet_wrap(outcome ~ hospital) - each unique combination will display in a separate panel, but they will not be arranged in a grid. The headings will show combined terms and these won’t be specific logic to the columns vs. rows. If you are providing only one faceting variable, a period . is used as a placeholder on the other side of the formula - see the code examples.\nFor facet_grid() you can also specify one or two columns to the formula (grid rows ~ columns). If you only want to specify one, you can place a period . on the other side of the tilde like facet_grid(. ~ hospital) or facet_grid(hospital ~ .).\n\nFacets can quickly contain an overwhelming amount of information - its good to ensure you don’t have too many levels of each variable that you choose to facet by. Here are some quick examples with the malaria dataset (see Download handbook and data) which consists of daily case counts of malaria for facilities, by age group.\nBelow we import and do some quick modifications for simplicity:\n\n# These data are daily counts of malaria cases, by facility-day\nmalaria_data <- import(here(\"data\", \"malaria_facility_count_data.rds\")) %>% # import\n select(-submitted_date, -Province, -newid) # remove unneeded columns\n\nThe first 50 rows of the malaria data are below. Note there is a column malaria_tot, but also columns for counts by age group (these will be used in the second, facet_grid() example).\n\n\n\n\n\n\n\nfacet_wrap()\nFor the moment, let’s focus on the columns malaria_tot and District. Ignore the age-specific count columns for now. We will plot epidemic curves with geom_col(), which produces a column for each day at the specified y-axis height given in column malaria_tot (the data are already daily counts, so we use geom_col() - see the “Bar plot” section below).\nWhen we add the command facet_wrap(), we specify a tilde and then the column to facet on (District in this case). You can place another column on the left side of the tilde, - this will create one facet for each combination - but we recommend you do this with facet_grid() instead. In this use case, one facet is created for each unique value of District.\n\n# A plot with facets by district\nggplot(malaria_data, aes(x = data_date, y = malaria_tot)) +\n geom_col(width = 1, fill = \"darkred\") + # plot the count data as columns\n theme_minimal()+ # simplify the background panels\n labs( # add plot labels, title, etc.\n x = \"Date of report\",\n y = \"Malaria cases\",\n title = \"Malaria cases by district\") +\n facet_wrap(~District) # the facets are created\n\n\n\n\n\n\n\n\n\n\nfacet_grid()\nWe can use a facet_grid() approach to cross two variables. Let’s say we want to cross District and age. Well, we need to do some data transformations on the age columns to get these data into ggplot-preferred “long” format. The age groups all have their own columns - we want them in a single column called age_group and another called num_cases. See the page on Pivoting data for more information on this process.\n\nmalaria_age <- malaria_data %>%\n select(-malaria_tot) %>% \n pivot_longer(\n cols = c(starts_with(\"malaria_rdt_\")), # choose columns to pivot longer\n names_to = \"age_group\", # column names become age group\n values_to = \"num_cases\" # values to a single column (num_cases)\n ) %>%\n mutate(\n age_group = str_replace(age_group, \"malaria_rdt_\", \"\"),\n age_group = forcats::fct_relevel(age_group, \"5-14\", after = 1))\n\nNow the first 50 rows of data look like this:\n\n\n\n\n\n\nWhen you pass the two variables to facet_grid(), easiest is to use formula notation (e.g. x ~ y) where x is rows and y is columns. Here is the plot, using facet_grid() to show the plots for each combination of the columns age_group and District.\n\nggplot(malaria_age, aes(x = data_date, y = num_cases)) +\n geom_col(fill = \"darkred\", width = 1) +\n theme_minimal()+\n labs(\n x = \"Date of report\",\n y = \"Malaria cases\",\n title = \"Malaria cases by district and age group\"\n ) +\n facet_grid(District ~ age_group)\n\n\n\n\n\n\n\n\n\n\nFree or fixed axes\nThe axes scales displayed when faceting are by default the same (fixed) across all the facets. This is helpful for cross-comparison, but not always appropriate.\nWhen using facet_wrap() or facet_grid(), we can add scales = \"free_y\" to “free” or release the y-axes of the panels to scale appropriately to their data subset. This is particularly useful if the actual counts are small for one of the subcategories and trends are otherwise hard to see. Instead of “free_y” we can also write “free_x” to do the same for the x-axis (e.g. for dates) or “free” for both axes. Note that in facet_grid, the y scales will be the same for facets in the same row, and the x scales will be the same for facets in the same column.\nWhen using facet_grid only, we can add space = \"free_y\" or space = \"free_x\" so that the actual height or width of the facet is weighted to the values of the figure within. This only works if scales = \"free\" (y or x) is already applied.\n\n# Free y-axis\nggplot(malaria_data, aes(x = data_date, y = malaria_tot)) +\n geom_col(width = 1, fill = \"darkred\") + # plot the count data as columns\n theme_minimal()+ # simplify the background panels\n labs( # add plot labels, title, etc.\n x = \"Date of report\",\n y = \"Malaria cases\",\n title = \"Malaria cases by district - 'free' x and y axes\") +\n facet_wrap(~District, scales = \"free\") # the facets are created\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFactor level order in facets\nSee this post on how to re-order factor levels within facets.", + "text": "30.6 Facets / Small-multiples\nFacets, or “small-multiples”, are used to split one plot into a multi-panel figure, with one panel (“facet”) per group of data. The same type of plot is created multiple times, each one using a sub-group of the same dataset.\nFaceting is a functionality that comes with ggplot2, so the legends and axes of the facet “panels” are automatically aligned. There are other packages discussed in the ggplot tips page that are used to combine completely different plots (with patchwork) into one figure.\nFaceting is done with one of the following ggplot2 functions:\n\nfacet_wrap() To show a different panel for each level of a single variable. One example of this could be showing a different epidemic curve for each hospital in a region. Facets are ordered alphabetically, unless the variable is a factor with other ordering defined.\n\n\n\nYou can invoke certain options to determine the layout of the facets, e.g. nrow = 1 or ncol = 1 to control the number of rows or columns that the faceted plots are arranged within.\n\n\nfacet_grid() This is used when you want to bring a second variable into the faceting arrangement. Here each panel of a grid shows the intersection between values in two columns. For example, epidemic curves for each hospital-age group combination with hospitals along the top (columns) and age groups along the sides (rows).\n\n\n\nnrow and ncol are not relevant, as the subgroups are presented in a grid.\n\nEach of these functions accept a formula syntax to specify the column(s) for faceting. Both accept up to two columns, one on each side of a tilde ~.\n\nFor facet_wrap() most often you will write only one column preceded by a tilde ~ like facet_wrap(~hospital). However you can write two columns facet_wrap(outcome ~ hospital) - each unique combination will display in a separate panel, but they will not be arranged in a grid. The headings will show combined terms and these won’t be specific logic to the columns vs. rows. If you are providing only one faceting variable, a period . is used as a placeholder on the other side of the formula - see the code examples.\nFor facet_grid() you can also specify one or two columns to the formula (grid rows ~ columns). If you only want to specify one, you can place a period . on the other side of the tilde like facet_grid(. ~ hospital) or facet_grid(hospital ~ .).\n\nFacets can quickly contain an overwhelming amount of information - its good to ensure you don’t have too many levels of each variable that you choose to facet by. Here are some quick examples with the malaria dataset (see Download handbook and data) which consists of daily case counts of malaria for facilities, by age group.\nBelow we import and do some quick modifications for simplicity:\n\n# These data are daily counts of malaria cases, by facility-day\nmalaria_data <- import(here(\"data\", \"malaria_facility_count_data.rds\")) %>% # import\n select(-submitted_date, -Province, -newid) # remove unneeded columns\n\nThe first 50 rows of the malaria data are below. Note there is a column malaria_tot, but also columns for counts by age group (these will be used in the second, facet_grid() example).\n\n\n\n\n\n\n\nfacet_wrap()\nFor the moment, let’s focus on the columns malaria_tot and District. Ignore the age-specific count columns for now. We will plot epidemic curves with geom_col(), which produces a column for each day at the specified y-axis height given in column malaria_tot (the data are already daily counts, so we use geom_col() - see the “Bar plot” section below).\nWhen we add the command facet_wrap(), we specify a tilde and then the column to facet on (District in this case). You can place another column on the left side of the tilde, - this will create one facet for each combination - but we recommend you do this with facet_grid() instead. In this use case, one facet is created for each unique value of District.\n\n# A plot with facets by district\nggplot(malaria_data, \n mapping = aes(x = data_date, y = malaria_tot)) +\n geom_col(width = 1, fill = \"darkred\") + # plot the count data as columns\n theme_minimal() + # simplify the background panels\n labs( # add plot labels, title, etc.\n x = \"Date of report\",\n y = \"Malaria cases\",\n title = \"Malaria cases by district\") +\n facet_wrap(~District) # the facets are created\n\n\n\n\n\n\n\n\n\n\nfacet_grid()\nWe can use a facet_grid() approach to cross two variables. Let’s say we want to cross District and age. Well, we need to do some data transformations on the age columns to get these data into ggplot-preferred “long” format. The age groups all have their own columns - we want them in a single column called age_group and another called num_cases. See the page on Pivoting data for more information on this process.\n\nmalaria_age <- malaria_data %>%\n select(-malaria_tot) %>% \n pivot_longer(\n cols = c(starts_with(\"malaria_rdt_\")), # choose columns to pivot longer\n names_to = \"age_group\", # column names become age group\n values_to = \"num_cases\" # values to a single column (num_cases)\n ) %>%\n mutate(\n age_group = str_replace(age_group, \"malaria_rdt_\", \"\"),\n age_group = forcats::fct_relevel(age_group, \"5-14\", after = 1))\n\nNow the first 50 rows of data look like this:\n\n\n\n\n\n\nWhen you pass the two variables to facet_grid(), easiest is to use formula notation (e.g. x ~ y) where x is rows and y is columns. Here is the plot, using facet_grid() to show the plots for each combination of the columns age_group and District.\n\nggplot(malaria_age, \n mapping = aes(x = data_date, y = num_cases)) +\n geom_col(fill = \"darkred\", width = 1) +\n theme_minimal() +\n labs(\n x = \"Date of report\",\n y = \"Malaria cases\",\n title = \"Malaria cases by district and age group\"\n ) +\n facet_grid(District ~ age_group)\n\n\n\n\n\n\n\n\n\n\nFree or fixed axes\nThe axes scales displayed when faceting are by default the same (fixed) across all the facets. This is helpful for cross-comparison, but not always appropriate.\nWhen using facet_wrap() or facet_grid(), we can add scales = \"free_y\" to “free” or release the y-axes of the panels to scale appropriately to their data subset. This is particularly useful if the actual counts are small for one of the subcategories and trends are otherwise hard to see. Instead of “free_y” we can also write “free_x” to do the same for the x-axis (e.g. for dates) or “free” for both axes. Note that in facet_grid, the y scales will be the same for facets in the same row, and the x scales will be the same for facets in the same column.\nWhen using facet_grid only, we can add space = \"free_y\" or space = \"free_x\" so that the actual height or width of the facet is weighted to the values of the figure within. This only works if scales = \"free\" (y or x) is already applied.\n\n# Free y-axis\nggplot(malaria_data, \n mapping = aes(x = data_date, y = malaria_tot)) +\n geom_col(width = 1, fill = \"darkred\") + # plot the count data as columns\n theme_minimal() + # simplify the background panels\n labs( # add plot labels, title, etc.\n x = \"Date of report\",\n y = \"Malaria cases\",\n title = \"Malaria cases by district - 'free' x and y axes\") +\n facet_wrap(~District, scales = \"free\") # the facets are created", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2716,7 +2716,7 @@ "href": "new_pages/ggplot_basics.html#storing-plots", "title": "30  ggplot basics", "section": "30.7 Storing plots", - "text": "30.7 Storing plots\n\nSaving plots\nBy default when you run a ggplot() command, the plot will be printed to the Plots RStudio pane. However, you can also save the plot as an object by using the assignment operator <- and giving it a name. Then it will not print unless the object name itself is run. You can also print it by wrapping the plot name with print(), but this is only necessary in certain circumstances such as if the plot is created inside a for loop used to print multiple plots at once (see Iteration, loops, and lists page).\n\n# define plot\nage_by_wt <- ggplot(data = linelist, mapping = aes(x = age_years, y = wt_kg, color = age_years))+\n geom_point(alpha = 0.1)\n\n# print\nage_by_wt \n\n\n\n\n\n\n\n\n\n\nModifying saved plots\nOne nice thing about ggplot2 is that you can define a plot (as above), and then add layers to it starting with its name. You do not have to repeat all the commands that created the original plot!\nFor example, to modify the plot age_by_wt that was defined above, to include a vertical line at age 50, we would just add a + and begin adding additional layers to the plot.\n\nage_by_wt+\n geom_vline(xintercept = 50)\n\n\n\n\n\n\n\n\n\n\nExporting plots\nExporting ggplots is made easy with the ggsave() function from ggplot2. It can work in two ways, either:\n\nSpecify the name of the plot object, then the file path and name with extension\n\nFor example: ggsave(my_plot, here(\"plots\", \"my_plot.png\"))\n\n\nRun the command with only a file path, to save the last plot that was printed\n\nFor example: ggsave(here(\"plots\", \"my_plot.png\"))\n\n\nYou can export as png, pdf, jpeg, tiff, bmp, svg, or several other file types, by specifying the file extension in the file path.\nYou can also specify the arguments width =, height =, and units = (either “in”, “cm”, or “mm”). You can also specify dpi = with a number for plot resolution (e.g. 300). See the function details by entering ?ggsave or reading the documentation online.\nRemember that you can use here() syntax to provide the desired file path. see the Import and export page for more information.", + "text": "30.7 Storing plots\n\nSaving plots\nBy default when you run a ggplot() command, the plot will be printed to the Plots RStudio pane. However, you can also save the plot as an object by using the assignment operator <- and giving it a name. Then it will not print unless the object name itself is run. You can also print it by wrapping the plot name with print(), but this is only necessary in certain circumstances such as if the plot is created inside a for loop used to print multiple plots at once (see Iteration, loops, and lists page).\n\n# define plot\nage_by_wt <- ggplot(data = linelist, \n mapping = aes(x = age_years, y = wt_kg, color = age_years)) +\n geom_point(alpha = 0.1)\n\n# print\nage_by_wt \n\n\n\n\n\n\n\n\n\n\nModifying saved plots\nOne nice thing about ggplot2 is that you can define a plot (as above), and then add layers to it starting with its name. You do not have to repeat all the commands that created the original plot!\nFor example, to modify the plot age_by_wt that was defined above, to include a vertical line at age 50, we would just add a + and begin adding additional layers to the plot.\n\nage_by_wt +\n geom_vline(xintercept = 50)\n\n\n\n\n\n\n\n\n\n\nExporting plots\nExporting ggplots is made easy with the ggsave() function from ggplot2. It can work in two ways, either:\n\nSpecify the name of the plot object, then the file path and name with extension.\n\nFor example: ggsave(my_plot, here(\"plots\", \"my_plot.png\")).\n\n\nRun the command with only a file path, to save the last plot that was printed.\n\nFor example: ggsave(here(\"plots\", \"my_plot.png\")).\n\n\nYou can export as png, pdf, jpeg, tiff, bmp, svg, or several other file types, by specifying the file extension in the file path.\nYou can also specify the arguments width =, height =, and units = (either “in”, “cm”, or “mm”). You can also specify dpi = with a number for plot resolution (e.g. 300). You can also change the the background of your plot by using the argument bg =, where you specify the colour, i.e. bg = \"white\". See the function details by entering?ggsave` or reading the documentation online.\nRemember that you can use here() syntax to provide the desired file path. see the Import and export page for more information.", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2727,7 +2727,7 @@ "href": "new_pages/ggplot_basics.html#labels", "title": "30  ggplot basics", "section": "30.8 Labels", - "text": "30.8 Labels\nSurely you will want to add or adjust the plot’s labels. These are most easily done within the labs() function which is added to the plot with + just as the geoms were.\nWithin labs() you can provide character strings to these arguements:\n\nx = and y = The x-axis and y-axis title (labels)\n\ntitle = The main plot title\n\nsubtitle = The subtitle of the plot, in smaller text below the title\n\ncaption = The caption of the plot, in bottom-right by default\n\nHere is a plot we made earlier, but with nicer labels:\n\nage_by_wt <- ggplot(\n data = linelist, # set data\n mapping = aes( # map aesthetics to column values\n x = age, # map x-axis to age \n y = wt_kg, # map y-axis to weight\n color = age))+ # map color to age\n geom_point()+ # display data as points\n labs(\n title = \"Age and weight distribution\",\n subtitle = \"Fictional Ebola outbreak, 2014\",\n x = \"Age in years\",\n y = \"Weight in kilos\",\n color = \"Age\",\n caption = stringr::str_glue(\"Data as of {max(linelist$date_hospitalisation, na.rm=T)}\"))\n\nage_by_wt\n\n\n\n\n\n\n\n\nNote how in the caption assignment we used str_glue() from the stringr package to implant dynamic R code within the string text. The caption will show the “Data as of:” date that reflects the maximum hospitalization date in the linelist. Read more about this in the page on Characters and strings.\nA note on specifying the legend title: There is no one “legend title” argument, as you could have multiple scales in your legend. Within labs(), you can write the argument for the plot aesthetic used to create the legend, and provide the title this way. For example, above we assigned color = age to create the legend. Therefore, we provide color = to labs() and assign the legend title desired (“Age” with capital A). If you create the legend with aes(fill = COLUMN), then in labs() you would write fill = to adjust the title of that legend. The section on color scales in the ggplot tips page provides more details on editing legends, and an alternative approach using scales_() functions.", + "text": "30.8 Labels\nSurely you will want to add or adjust the plot’s labels. These are most easily done within the labs() function which is added to the plot with + just as the geoms were.\nWithin labs() you can provide character strings to these arguements:\n\nx = and y = The x-axis and y-axis title (labels).\n\ntitle = The main plot title.\n\nsubtitle = The subtitle of the plot, in smaller text below the title.\n\ncaption = The caption of the plot, in bottom-right by default.\n\nHere is a plot we made earlier, but with nicer labels:\n\nage_by_wt <- ggplot(\n data = linelist, # set data\n mapping = aes( # map aesthetics to column values\n x = age, # map x-axis to age \n y = wt_kg, # map y-axis to weight\n color = age)) + # map color to age\n geom_point() + # display data as points\n labs(\n title = \"Age and weight distribution\",\n subtitle = \"Fictional Ebola outbreak, 2014\",\n x = \"Age in years\",\n y = \"Weight in kilos\",\n color = \"Age\",\n caption = stringr::str_glue(\"Data as of {max(linelist$date_hospitalisation, na.rm=T)}\"))\n\nage_by_wt\n\n\n\n\n\n\n\n\nNote how in the caption assignment we used str_glue() from the stringr package to implant dynamic R code within the string text. The caption will show the “Data as of:” date that reflects the maximum hospitalization date in the linelist. Read more about this in the page on Characters and strings.\nA note on specifying the legend title: There is no one “legend title” argument, as you could have multiple scales in your legend. Within labs(), you can write the argument for the plot aesthetic used to create the legend, and provide the title this way. For example, above we assigned color = age to create the legend. Therefore, we provide color = to labs() and assign the legend title desired (“Age” with capital A). If you create the legend with aes(fill = COLUMN), then in labs() you would write fill = to adjust the title of that legend. The section on color scales in the ggplot tips page provides more details on editing legends, and an alternative approach using scales_() functions.", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2738,7 +2738,7 @@ "href": "new_pages/ggplot_basics.html#ggplot_basics_themes", "title": "30  ggplot basics", "section": "30.9 Themes", - "text": "30.9 Themes\nOne of the best parts of ggplot2 is the amount of control you have over the plot - you can define anything! As mentioned above, the design of the plot that is not related to the data shapes/geometries are adjusted within the theme() function. For example, the plot background color, presence/absence of gridlines, and the font/size/color/alignment of text (titles, subtitles, captions, axis text…). These adjustments can be done in one of two ways:\n\nAdd a complete theme theme_() function to make sweeping adjustments - these include theme_classic(), theme_minimal(), theme_dark(), theme_light() theme_grey(), theme_bw() among others\n\nAdjust each tiny aspect of the plot individually within theme()\n\n\nComplete themes\nAs they are quite straight-forward, we will demonstrate the complete theme functions below and will not describe them further here. Note that any micro-adjustments with theme() should be made after use of a complete theme.\nWrite them with empty parentheses.\n\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2)+\n labs(title = \"Theme classic\")+\n theme_classic()\n\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2)+\n labs(title = \"Theme bw\")+\n theme_bw()\n\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2)+\n labs(title = \"Theme minimal\")+\n theme_minimal()\n\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2)+\n labs(title = \"Theme gray\")+\n theme_gray()\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nModify theme\nThe theme() function can take a large number of arguments, each of which edits a very specific aspect of the plot. There is no way we could cover all of the arguments, but we will describe the general pattern for them and show you how to find the argument name that you need. The basic syntax is this:\n\nWithin theme() write the argument name for the plot element you want to edit, like plot.title =\n\nProvide an element_() function to the argument\n\n\n\nMost often, use element_text(), but others include element_rect() for canvas background colors, or element_blank() to remove plot elements\n\n\n\nWithin the element_() function, write argument assignments to make the fine adjustments you desire\n\nSo, that description was quite abstract, so here are some examples.\nThe below plot looks quite silly, but it serves to show you a variety of the ways you can adjust your plot.\n\nWe begin with the plot age_by_wt defined just above and add theme_classic()\n\nFor finer adjustments we add theme() and include one argument for each plot element to adjust\n\nIt can be nice to organize the arguments in logical sections. To describe just some of those used below:\n\nlegend.position = is unique in that it accepts simple values like “bottom”, “top”, “left”, and “right”. But generally, text-related arguments require that you place the details within element_text().\n\nTitle size with element_text(size = 30)\n\nThe caption horizontal alignment with element_text(hjust = 0) (from right to left)\n\nThe subtitle is italicized with element_text(face = \"italic\")\n\n\nage_by_wt + \n theme_classic()+ # pre-defined theme adjustments\n theme(\n legend.position = \"bottom\", # move legend to bottom\n \n plot.title = element_text(size = 30), # size of title to 30\n plot.caption = element_text(hjust = 0), # left-align caption\n plot.subtitle = element_text(face = \"italic\"), # italicize subtitle\n \n axis.text.x = element_text(color = \"red\", size = 15, angle = 90), # adjusts only x-axis text\n axis.text.y = element_text(size = 15), # adjusts only y-axis text\n \n axis.title = element_text(size = 20) # adjusts both axes titles\n ) \n\n\n\n\n\n\n\n\nHere are some especially common theme() arguments. You will recognize some patterns, such as appending .x or .y to apply the change only to one axis.\n\n\n\n\n\n\n\ntheme() argument\nWhat it adjusts\n\n\n\n\nplot.title = element_text()\nThe title\n\n\nplot.subtitle = element_text()\nThe subtitle\n\n\nplot.caption = element_text()\nThe caption (family, face, color, size, angle, vjust, hjust…)\n\n\naxis.title = element_text()\nAxis titles (both x and y) (size, face, angle, color…)\n\n\naxis.title.x = element_text()\nAxis title x-axis only (use .y for y-axis only)\n\n\naxis.text = element_text()\nAxis text (both x and y)\n\n\naxis.text.x = element_text()\nAxis text x-axis only (use .y for y-axis only)\n\n\naxis.ticks = element_blank()\nRemove axis ticks\n\n\naxis.line = element_line()\nAxis lines (colour, size, linetype: solid dashed dotted etc)\n\n\nstrip.text = element_text()\nFacet strip text (colour, face, size, angle…)\n\n\nstrip.background = element_rect()\nfacet strip (fill, colour, size…)\n\n\n\nBut there are so many theme arguments! How could I remember them all? Do not worry - it is impossible to remember them all. Luckily there are a few tools to help you:\nThe tidyverse documentation on modifying theme, which has a complete list.\nTIP: Run theme_get() from ggplot2 to print a list of all 90+ theme() arguments to the console.\nTIP: If you ever want to remove an element of a plot, you can also do it through theme(). Just pass element_blank() to an argument to have it disappear completely. For legends, set legend.position = \"none\".", + "text": "30.9 Themes\nOne of the best parts of ggplot2 is the amount of control you have over the plot - you can define anything! As mentioned above, the design of the plot that is not related to the data shapes/geometries are adjusted within the theme() function. For example, the plot background color, presence/absence of gridlines, and the font/size/color/alignment of text (titles, subtitles, captions, axis text…). These adjustments can be done in one of two ways:\n\nAdd a complete theme. The function theme_() makes sweeping adjustments - these include: theme_classic(), theme_minimal(), theme_dark(), theme_light() theme_grey(), theme_bw() among others.\n\nAdjust each tiny aspect of the plot individually within theme().\n\n\nComplete themes\nAs they are quite straight-forward, we will demonstrate the complete theme functions below and will not describe them further here. Note that any micro-adjustments with theme() should be made after use of a complete theme.\nWrite them with empty parentheses.\n\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2) +\n labs(title = \"Theme classic\") +\n theme_classic()\n\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2) +\n labs(title = \"Theme bw\") +\n theme_bw()\n\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2) +\n labs(title = \"Theme minimal\") +\n theme_minimal()\n\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(color = \"darkgreen\", size = 0.5, alpha = 0.2) +\n labs(title = \"Theme gray\") +\n theme_gray()\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nModify theme\nThe theme() function can take a large number of arguments, each of which edits a very specific aspect of the plot. There is no way we could cover all of the arguments, but we will describe the general pattern for them and show you how to find the argument name that you need. The basic syntax is this:\n\nWithin theme() write the argument name for the plot element you want to edit, like plot.title =.\n\nProvide an element_() function to the argument.\n\nMost often, use element_text(), but others include element_rect() for canvas background colors, or element_blank() to remove plot elements.\n\n\nWithin the element_() function, write argument assignments to make the fine adjustments you desire.\n\nSo, that description was quite abstract, so here are some examples.\nThe below plot looks quite silly, but it serves to show you a variety of the ways you can adjust your plot.\n\nWe begin with the plot age_by_wt defined just above and add theme_classic().\n\nFor finer adjustments we add theme() and include one argument for each plot element to adjust.\n\nIt can be nice to organize the arguments in logical sections. To describe just some of those used below:\n\nlegend.position = is unique in that it accepts simple values like “bottom”, “top”, “left”, and “right”. But generally, text-related arguments require that you place the details within element_text().\n\nTitle size with element_text(size = 30).\n\nThe caption horizontal alignment with element_text(hjust = 0) (from right to left).\n\nThe subtitle is italicized with element_text(face = \"italic\").\n\n\nage_by_wt + \n theme_classic() + # pre-defined theme adjustments\n theme(\n legend.position = \"bottom\", # move legend to bottom\n \n plot.title = element_text(size = 30), # size of title to 30\n plot.caption = element_text(hjust = 0), # left-align caption\n plot.subtitle = element_text(face = \"italic\"), # italicize subtitle\n \n axis.text.x = element_text(color = \"red\", size = 15, angle = 90), # adjusts only x-axis text\n axis.text.y = element_text(size = 15), # adjusts only y-axis text\n \n axis.title = element_text(size = 20) # adjusts both axes titles\n ) \n\n\n\n\n\n\n\n\nHere are some especially common theme() arguments. You will recognize some patterns, such as appending .x or .y to apply the change only to one axis.\n\n\n\n\n\n\n\ntheme() argument\nWhat it adjusts\n\n\n\n\nplot.title = element_text()\nThe title\n\n\nplot.subtitle = element_text()\nThe subtitle\n\n\nplot.caption = element_text()\nThe caption (family, face, color, size, angle, vjust, hjust…)\n\n\naxis.title = element_text()\nAxis titles (both x and y) (size, face, angle, color…)\n\n\naxis.title.x = element_text()\nAxis title x-axis only (use .y for y-axis only)\n\n\naxis.text = element_text()\nAxis text (both x and y)\n\n\naxis.text.x = element_text()\nAxis text x-axis only (use .y for y-axis only)\n\n\naxis.ticks = element_blank()\nRemove axis ticks\n\n\naxis.line = element_line()\nAxis lines (colour, size, linetype: solid dashed dotted etc)\n\n\nstrip.text = element_text()\nFacet strip text (colour, face, size, angle…)\n\n\nstrip.background = element_rect()\nfacet strip (fill, colour, size…)\n\n\n\nBut there are so many theme arguments! How could I remember them all? Do not worry - it is impossible to remember them all. Luckily there are a few tools to help you:\nThe tidyverse documentation on modifying theme, which has a complete list.\nTIP: Run theme_get() from ggplot2 to print a list of all 90+ theme() arguments to the console.\nTIP: If you ever want to remove an element of a plot, you can also do it through theme(). Just pass element_blank() to an argument to have it disappear completely. For legends, set legend.position = \"none\".", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2760,7 +2760,7 @@ "href": "new_pages/ggplot_basics.html#piping-into-ggplot2", "title": "30  ggplot basics", "section": "30.11 Piping into ggplot2", - "text": "30.11 Piping into ggplot2\nWhen using pipes to clean and transform your data, it is easy to pass the transformed data into ggplot().\nThe pipes that pass the dataset from function-to-function will transition to + once the ggplot() function is called. Note that in this case, there is no need to specify the data = argument, as this is automatically defined as the piped-in dataset.\nThis is how that might look:\n\nlinelist %>% # begin with linelist\n select(c(case_id, fever, chills, cough, aches, vomit)) %>% # select columns\n pivot_longer( # pivot longer\n cols = -case_id, \n names_to = \"symptom_name\",\n values_to = \"symptom_is_present\") %>%\n mutate( # replace missing values\n symptom_is_present = replace_na(symptom_is_present, \"unknown\")) %>% \n \n ggplot( # begin ggplot!\n mapping = aes(x = symptom_name, fill = symptom_is_present))+\n geom_bar(position = \"fill\", col = \"black\") + \n theme_classic() +\n labs(\n x = \"Symptom\",\n y = \"Symptom status (proportion)\"\n )", + "text": "30.11 Piping into ggplot2\nWhen using pipes to clean and transform your data, it is easy to pass the transformed data into ggplot().\nThe pipes that pass the dataset from function-to-function will transition to + once the ggplot() function is called. Note that in this case, there is no need to specify the data = argument, as this is automatically defined as the piped-in dataset.\nThis is how that might look:\n\nlinelist %>% # begin with linelist\n select(c(case_id, fever, chills, cough, aches, vomit)) %>% # select columns\n pivot_longer( # pivot longer\n cols = -case_id, \n names_to = \"symptom_name\",\n values_to = \"symptom_is_present\") %>%\n mutate( # replace missing values\n symptom_is_present = replace_na(symptom_is_present, \"unknown\")) %>% \n \n ggplot( # begin ggplot!\n mapping = aes(x = symptom_name, fill = symptom_is_present)) +\n geom_bar(position = \"fill\", col = \"black\") + \n theme_classic() +\n labs(\n x = \"Symptom\",\n y = \"Symptom status (proportion)\"\n )", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2771,7 +2771,7 @@ "href": "new_pages/ggplot_basics.html#plot-continuous-data", "title": "30  ggplot basics", "section": "30.12 Plot continuous data", - "text": "30.12 Plot continuous data\nThroughout this page, you have already seen many examples of plotting continuous data. Here we briefly consolidate these and present a few variations.\nVisualisations covered here include:\n\nPlots for one continuous variable:\n\nHistogram, a classic graph to present the distribution of a continuous variable.\nBox plot (also called box and whisker), to show the 25th, 50th, and 75th percentiles, tail ends of the distribution, and outliers (important limitations).\n\nJitter plot, to show all values as points that are ‘jittered’ so they can (mostly) all be seen, even where two have the same value.\n\nViolin plot, show the distribution of a continuous variable based on the symmetrical width of the ‘violin’.\nSina plot, are a combination of jitter and violin plots, where individual points are shown but in the symmetrical shape of the distribution (via ggforce package).\n\n\nScatter plot for two continuous variables.\n\nHeat plots for three continuous variables (linked to Heat plots page)\n\n\nHistograms\nHistograms may look like bar charts, but are distinct because they measure the distribution of a continuous variable. There are no spaces between the “bars”, and only one column is provided to geom_histogram().\nBelow is code for generating histograms, which group continuous data into ranges and display in adjacent bars of varying height. This is done using geom_histogram(). See the “Bar plot” section of the ggplot basics page to understand difference between geom_histogram(), geom_bar(), and geom_col().\nWe will show the distribution of ages of cases. Within mapping = aes() specify which column you want to see the distribution of. You can assign this column to either the x or the y axis.\nThe rows will be assigned to “bins” based on their numeric age, and these bins will be graphically represented by bars. If you specify a number of bins with the bins = plot aesthetic, the break points are evenly spaced between the minimum and maximum values of the histogram. If bins = is unspecified, an appropriate number of bins will be guessed and this message displayed after the plot:\n## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.\nIf you do not want to specify a number of bins to bins =, you could alternatively specify binwidth = in the units of the axis. We give a few examples showing different bins and bin widths:\n\n# A) Regular histogram\nggplot(data = linelist, aes(x = age))+ # provide x variable\n geom_histogram()+\n labs(title = \"A) Default histogram (30 bins)\")\n\n# B) More bins\nggplot(data = linelist, aes(x = age))+ # provide x variable\n geom_histogram(bins = 50)+\n labs(title = \"B) Set to 50 bins\")\n\n# C) Fewer bins\nggplot(data = linelist, aes(x = age))+ # provide x variable\n geom_histogram(bins = 5)+\n labs(title = \"C) Set to 5 bins\")\n\n\n# D) More bins\nggplot(data = linelist, aes(x = age))+ # provide x variable\n geom_histogram(binwidth = 1)+\n labs(title = \"D) binwidth of 1\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTo get smoothed proportions, you can use geom_density():\n\n# Frequency with proportion axis, smoothed\nggplot(data = linelist, mapping = aes(x = age)) +\n geom_density(size = 2, alpha = 0.2)+\n labs(title = \"Proportional density\")\n\n# Stacked frequency with proportion axis, smoothed\nggplot(data = linelist, mapping = aes(x = age, fill = gender)) +\n geom_density(size = 2, alpha = 0.2, position = \"stack\")+\n labs(title = \"'Stacked' proportional densities\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTo get a “stacked” histogram (of a continuous column of data), you can do one of the following:\n\nUse geom_histogram() with the fill = argument within aes() and assigned to the grouping column, or\n\nUse geom_freqpoly(), which is likely easier to read (you can still set binwidth =)\n\nTo see proportions of all values, set the y = after_stat(density) (use this syntax exactly - not changed for your data). Note: these proportions will show per group.\n\nEach is shown below (*note use of color = vs. fill = in each):\n\n# \"Stacked\" histogram\nggplot(data = linelist, mapping = aes(x = age, fill = gender)) +\n geom_histogram(binwidth = 2)+\n labs(title = \"'Stacked' histogram\")\n\n# Frequency \nggplot(data = linelist, mapping = aes(x = age, color = gender)) +\n geom_freqpoly(binwidth = 2, size = 2)+\n labs(title = \"Freqpoly\")\n\n# Frequency with proportion axis\nggplot(data = linelist, mapping = aes(x = age, y = after_stat(density), color = gender)) +\n geom_freqpoly(binwidth = 5, size = 2)+\n labs(title = \"Proportional freqpoly\")\n\n# Frequency with proportion axis, smoothed\nggplot(data = linelist, mapping = aes(x = age, y = after_stat(density), fill = gender)) +\n geom_density(size = 2, alpha = 0.2)+\n labs(title = \"Proportional, smoothed with geom_density()\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nIf you want to have some fun, try geom_density_ridges from the ggridges package (vignette here.\nRead more in detail about histograms at the tidyverse page on geom_histogram().\n\n\nBox plots\nBox plots are common, but have important limitations. They can obscure the actual distribution - e.g. a bi-modal distribution. See this R graph gallery and this data-to-viz article for more details. However, they do nicely display the inter-quartile range and outliers - so they can be overlaid on top of other types of plots that show the distribution in more detail.\nBelow we remind you of the various components of a boxplot:\n\n\n\n\n\n\n\n\n\nWhen using geom_boxplot() to create a box plot, you generally map only one axis (x or y) within aes(). The axis specified determines if the plots are horizontal or vertical.\nIn most geoms, you create a plot per group by mapping an aesthetic like color = or fill = to a column within aes(). However, for box plots achieve this by assigning the grouping column to the un-assigned axis (x or y). Below is code for a boxplot of all age values in the dataset, and second is code to display one box plot for each (non-missing) gender in the dataset. Note that NA (missing) values will appear as a separate box plot unless removed. In this example we also set the fill to the column outcome so each plot is a different color - but this is not necessary.\n\n# A) Overall boxplot\nggplot(data = linelist)+ \n geom_boxplot(mapping = aes(y = age))+ # only y axis mapped (not x)\n labs(title = \"A) Overall boxplot\")\n\n# B) Box plot by group\nggplot(data = linelist, mapping = aes(y = age, x = gender, fill = gender)) + \n geom_boxplot()+ \n theme(legend.position = \"none\")+ # remove legend (redundant)\n labs(title = \"B) Boxplot by gender\") \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFor code to add a box plot to the edges of a scatter plot (“marginal” plots) see the page ggplot tips.\n\n\nViolin, jitter, and sina plots\nBelow is code for creating violin plots (geom_violin) and jitter plots (geom_jitter) to show distributions. You can specify that the fill or color is also determined by the data, by inserting these options within aes().\n\n# A) Jitter plot by group\nggplot(data = linelist %>% drop_na(outcome), # remove missing values\n mapping = aes(y = age, # Continuous variable\n x = outcome, # Grouping variable\n color = outcome))+ # Color variable\n geom_jitter()+ # Create the violin plot\n labs(title = \"A) jitter plot by gender\") \n\n\n\n# B) Violin plot by group\nggplot(data = linelist %>% drop_na(outcome), # remove missing values\n mapping = aes(y = age, # Continuous variable\n x = outcome, # Grouping variable\n fill = outcome))+ # fill variable (color)\n geom_violin()+ # create the violin plot\n labs(title = \"B) violin plot by gender\") \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYou can combine the two using the geom_sina() function from the ggforce package. The sina plots the jitter points in the shape of the violin plot. When overlaid on the violin plot (adjusting the transparencies) this can be easier to visually interpret.\n\n# A) Sina plot by group\nggplot(\n data = linelist %>% drop_na(outcome), \n aes(y = age, # numeric variable\n x = outcome)) + # group variable\n geom_violin(\n aes(fill = outcome), # fill (color of violin background)\n color = \"white\", # white outline\n alpha = 0.2)+ # transparency\n geom_sina(\n size=1, # Change the size of the jitter\n aes(color = outcome))+ # color (color of dots)\n scale_fill_manual( # Define fill for violin background by death/recover\n values = c(\"Death\" = \"#bf5300\", \n \"Recover\" = \"#11118c\")) + \n scale_color_manual( # Define colours for points by death/recover\n values = c(\"Death\" = \"#bf5300\", \n \"Recover\" = \"#11118c\")) + \n theme_minimal() + # Remove the gray background\n theme(legend.position = \"none\") + # Remove unnecessary legend\n labs(title = \"B) violin and sina plot by gender, with extra formatting\") \n\n\n\n\n\n\n\n\n\n\nTwo continuous variables\nFollowing similar syntax, geom_point() will allow you to plot two continuous variables against each other in a scatter plot. This is useful for showing actual values rather than their distributions. A basic scatter plot of age vs weight is shown in (A). In (B) we again use facet_grid() to show the relationship between two continuous variables in the linelist.\n\n# Basic scatter plot of weight and age\nggplot(data = linelist, \n mapping = aes(y = wt_kg, x = age))+\n geom_point() +\n labs(title = \"A) Scatter plot of weight and age\")\n\n# Scatter plot of weight and age by gender and Ebola outcome\nggplot(data = linelist %>% drop_na(gender, outcome), # filter retains non-missing gender/outcome\n mapping = aes(y = wt_kg, x = age))+\n geom_point() +\n labs(title = \"B) Scatter plot of weight and age faceted by gender and outcome\")+\n facet_grid(gender ~ outcome) \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThree continuous variables\nYou can display three continuous variables by utilizing the fill = argument to create a heat plot. The color of each “cell” will reflect the value of the third continuous column of data. See the ggplot tips page and the page on on [Heat plots] for more details and several examples.\nThere are ways to make 3D plots in R, but for applied epidemiology these are often difficult to interpret and therefore less useful for decision-making.", + "text": "30.12 Plot continuous data\nThroughout this page, you have already seen many examples of plotting continuous data. Here we briefly consolidate these and present a few variations.\nVisualisations covered here include:\n\nPlots for one continuous variable:\n\nHistogram, a classic graph to present the distribution of a continuous variable.\nBox plot (also called box and whisker), to show the 25th, 50th, and 75th percentiles, tail ends of the distribution, and outliers (important limitations).\n\nJitter plot, to show all values as points that are ‘jittered’ so they can (mostly) all be seen, even where two have the same value.\n\nViolin plot, show the distribution of a continuous variable based on the symmetrical width of the ‘violin’.\nSina plot, are a combination of jitter and violin plots, where individual points are shown but in the symmetrical shape of the distribution (via ggforce package).\n\n\nScatter plot for two continuous variables.\n\nHeat plots for three continuous variables (linked to Heat plots page).\n\n\nHistograms\nHistograms may look like bar charts, but are distinct because they measure the distribution of a continuous variable. There are no spaces between the “bars”, and only one column is provided to geom_histogram().\nBelow is code for generating histograms, which group continuous data into ranges and display in adjacent bars of varying height. This is done using geom_histogram(). See the “Bar plot” section of the ggplot basics page to understand difference between geom_histogram(), geom_bar(), and geom_col().\nWe will show the distribution of ages of cases. Within mapping = aes() specify which column you want to see the distribution of. You can assign this column to either the x or the y axis.\nThe rows will be assigned to “bins” based on their numeric age, and these bins will be graphically represented by bars. If you specify a number of bins with the bins = plot aesthetic, the break points are evenly spaced between the minimum and maximum values of the histogram. If bins = is unspecified, an appropriate number of bins will be guessed and this message displayed after the plot:\n## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.\nIf you do not want to specify a number of bins to bins =, you could alternatively specify binwidth = in the units of the axis. We give a few examples showing different bins and bin widths:\n\n# A) Regular histogram\nggplot(data = linelist, \n mapping = aes(x = age)) + # provide x variable\n geom_histogram() +\n labs(title = \"A) Default histogram (30 bins)\")\n\n# B) More bins\nggplot(data = linelist, \n mapping = aes(x = age)) + # provide x variable\n geom_histogram(bins = 50) +\n labs(title = \"B) Set to 50 bins\")\n\n# C) Fewer bins\nggplot(data = linelist, \n mapping = aes(x = age)) + # provide x variable\n geom_histogram(bins = 5) +\n labs(title = \"C) Set to 5 bins\")\n\n\n# D) More bins\nggplot(data = linelist, \n mapping = aes(x = age)) + # provide x variable\n geom_histogram(binwidth = 1) +\n labs(title = \"D) binwidth of 1\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTo get smoothed proportions, you can use geom_density():\n\n# Frequency with proportion axis, smoothed\nggplot(data = linelist, \n mapping = aes(x = age)) +\n geom_density(size = 2, alpha = 0.2) +\n labs(title = \"Proportional density\")\n\n# Stacked frequency with proportion axis, smoothed\nggplot(data = linelist, \n mapping = aes(x = age, fill = gender)) +\n geom_density(size = 2, alpha = 0.2, position = \"stack\") +\n labs(title = \"'Stacked' proportional densities\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTo get a “stacked” histogram (of a continuous column of data), you can do one of the following:\n\nUse geom_histogram() with the fill = argument within aes() and assigned to the grouping column, or,\n\nUse geom_freqpoly(), which is likely easier to read (you can still set binwidth =).\n\nTo see proportions of all values, set the y = after_stat(density) (use this syntax exactly - not changed for your data). Note: these proportions will show per group.\n\nEach is shown below (*note use of color = vs. fill = in each):\n\n# \"Stacked\" histogram\nggplot(data = linelist, \n mapping = aes(x = age, fill = gender)) +\n geom_histogram(binwidth = 2) +\n labs(title = \"'Stacked' histogram\")\n\n# Frequency \nggplot(data = linelist, \n mapping = aes(x = age, color = gender)) +\n geom_freqpoly(binwidth = 2, size = 2) +\n labs(title = \"Freqpoly\")\n\n# Frequency with proportion axis\nggplot(data = linelist, \n mapping = aes(x = age, y = after_stat(density), color = gender)) +\n geom_freqpoly(binwidth = 5, size = 2) +\n labs(title = \"Proportional freqpoly\")\n\n# Frequency with proportion axis, smoothed\nggplot(data = linelist, \n mapping = aes(x = age, y = after_stat(density), fill = gender)) +\n geom_density(size = 2, alpha = 0.2) +\n labs(title = \"Proportional, smoothed with geom_density()\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nIf you want to have some fun, try geom_density_ridges from the ggridges package ([vignette here])(https://cran.r-project.org/web/packages/ggridges/vignettes/introduction.html).\nRead more in detail about histograms at the tidyverse page on geom_histogram().\n\n\nBox plots\nBox plots are common, but have important limitations. They can obscure the actual distribution - e.g. a bi-modal distribution. See this R graph gallery and this data-to-viz article for more details. However, they do nicely display the inter-quartile range and outliers - so they can be overlaid on top of other types of plots that show the distribution in more detail.\nBelow we remind you of the various components of a boxplot:\n\n\n\n\n\n\n\n\n\nWhen using geom_boxplot() to create a box plot, you generally map only one axis (x or y) within aes(). The axis specified determines if the plots are horizontal or vertical.\nIn most geoms, you create a plot per group by mapping an aesthetic like color = or fill = to a column within aes(). However, for box plots achieve this by assigning the grouping column to the un-assigned axis (x or y). Below is code for a boxplot of all age values in the dataset, and second is code to display one box plot for each (non-missing) gender in the dataset. Note that NA (missing) values will appear as a separate box plot unless removed. In this example we also set the fill to the column outcome so each plot is a different color - but this is not necessary.\n\n# A) Overall boxplot\nggplot(data = linelist) + \n geom_boxplot(mapping = aes(y = age)) + # only y axis mapped (not x)\n labs(title = \"A) Overall boxplot\")\n\n# B) Box plot by group\nggplot(data = linelist, mapping = aes(y = age, x = gender, fill = gender)) + \n geom_boxplot() + \n theme(legend.position = \"none\") + # remove legend (redundant)\n labs(title = \"B) Boxplot by gender\") \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFor code to add a box plot to the edges of a scatter plot (“marginal” plots) see the page ggplot tips.\n\n\nViolin, jitter, and sina plots\nBelow is code for creating violin plots (geom_violin) and jitter plots (geom_jitter) to show distributions. You can specify that the fill or color is also determined by the data, by inserting these options within aes().\n\n# A) Jitter plot by group\nggplot(data = linelist %>% drop_na(outcome), # remove missing values\n mapping = aes(y = age, # Continuous variable\n x = outcome, # Grouping variable\n color = outcome)) + # Color variable\n geom_jitter() + # Create the violin plot\n labs(title = \"A) jitter plot by gender\") \n\n\n\n# B) Violin plot by group\nggplot(data = linelist %>% drop_na(outcome), # remove missing values\n mapping = aes(y = age, # Continuous variable\n x = outcome, # Grouping variable\n fill = outcome)) + # fill variable (color)\n geom_violin() + # create the violin plot\n labs(title = \"B) violin plot by gender\") \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYou can combine the two using the geom_sina() function from the ggforce package. The sina plots the jitter points in the shape of the violin plot. When overlaid on the violin plot (adjusting the transparencies) this can be easier to visually interpret.\n\n# A) Sina plot by group\nggplot(\n data = linelist %>% drop_na(outcome), \n mapping = aes(y = age, # numeric variable\n x = outcome)) + # group variable\n geom_violin(\n mapping = aes(fill = outcome), # fill (color of violin background)\n color = \"white\", # white outline\n alpha = 0.2) + # transparency\n geom_sina(\n size=1, # Change the size of the jitter\n mapping = aes(color = outcome)) + # color (color of dots)\n scale_fill_manual( # Define fill for violin background by death/recover\n values = c(\"Death\" = \"#bf5300\", \n \"Recover\" = \"#11118c\")) + \n scale_color_manual( # Define colours for points by death/recover\n values = c(\"Death\" = \"#bf5300\", \n \"Recover\" = \"#11118c\")) + \n theme_minimal() + # Remove the gray background\n theme(legend.position = \"none\") + # Remove unnecessary legend\n labs(title = \"B) violin and sina plot by gender, with extra formatting\") \n\n\n\n\n\n\n\n\n\n\nTwo continuous variables\nFollowing similar syntax, geom_point() will allow you to plot two continuous variables against each other in a scatter plot. This is useful for showing actual values rather than their distributions. A basic scatter plot of age vs weight is shown in (A). In (B) we again use facet_grid() to show the relationship between two continuous variables in the linelist.\n\n# Basic scatter plot of weight and age\nggplot(data = linelist, \n mapping = aes(y = wt_kg, x = age)) +\n geom_point() +\n labs(title = \"A) Scatter plot of weight and age\")\n\n# Scatter plot of weight and age by gender and Ebola outcome\nggplot(data = linelist %>% drop_na(gender, outcome), # filter retains non-missing gender/outcome\n mapping = aes(y = wt_kg, x = age)) +\n geom_point() +\n labs(title = \"B) Scatter plot of weight and age faceted by gender and outcome\") +\n facet_grid(gender ~ outcome) \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThree continuous variables\nYou can display three continuous variables by utilizing the fill = argument to create a heat plot. The color of each “cell” will reflect the value of the third continuous column of data. See the ggplot tips page and the page on on Heat plots for more details and several examples.\nThere are ways to make 3D plots in R, but for applied epidemiology these are often difficult to interpret and therefore less useful for decision-making.", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2782,7 +2782,7 @@ "href": "new_pages/ggplot_basics.html#plot-categorical-data", "title": "30  ggplot basics", "section": "30.13 Plot categorical data", - "text": "30.13 Plot categorical data\nCategorical data can be character values, could be logical (TRUE/FALSE), or factors (see the [Factors] page).\n\nPreparation\n\nData structure\nThe first thing to understand about your categorical data is whether it exists as raw observations like a linelist of cases, or as a summary or aggregate data frame that holds counts or proportions. The state of your data will impact which plotting function you use:\n\nIf your data are raw observations with one row per observation, you will likely use geom_bar()\n\nIf your data are already aggregated into counts or proportions, you will likely use geom_col()\n\n\n\nColumn class and value ordering\nNext, examine the class of the columns you want to plot. We look at hospital, first with class() from base R, and with tabyl() from janitor.\n\n# View class of hospital column - we can see it is a character\nclass(linelist$hospital)\n\n[1] \"character\"\n\n# Look at values and proportions within hospital column\nlinelist %>% \n tabyl(hospital)\n\n hospital n percent\n Central Hospital 454 0.07710598\n Military Hospital 896 0.15217391\n Missing 1469 0.24949049\n Other 885 0.15030571\n Port Hospital 1762 0.29925272\n St. Mark's Maternity Hospital (SMMH) 422 0.07167120\n\n\nWe can see the values within are characters, as they are hospital names, and by default they are ordered alphabetically. There are ‘other’ and ‘missing’ values, which we would prefer to be the last subcategories when presenting breakdowns. So we change this column into a factor and re-order it. This is covered in more detail in the Factors page.\n\n# Convert to factor and define level order so \"Other\" and \"Missing\" are last\nlinelist <- linelist %>% \n mutate(\n hospital = fct_relevel(hospital, \n \"St. Mark's Maternity Hospital (SMMH)\",\n \"Port Hospital\", \n \"Central Hospital\",\n \"Military Hospital\",\n \"Other\",\n \"Missing\"))\n\n\nlevels(linelist$hospital)\n\n[1] \"St. Mark's Maternity Hospital (SMMH)\"\n[2] \"Port Hospital\" \n[3] \"Central Hospital\" \n[4] \"Military Hospital\" \n[5] \"Other\" \n[6] \"Missing\" \n\n\n\n\n\ngeom_bar()\nUse geom_bar() if you want bar height (or the height of stacked bar components) to reflect the number of relevant rows in the data. These bars will have gaps between them, unless the width = plot aesthetic is adjusted.\n\nProvide only one axis column assignment (typically x-axis). If you provide x and y, you will get Error: stat_count() can only have an x or y aesthetic.\n\nYou can create stacked bars by adding a fill = column assignment within mapping = aes()\n\nThe opposite axis will be titled “count” by default, because it represents the number of rows\n\nBelow, we have assigned outcome to the y-axis, but it could just as easily be on the x-axis. If you have longer character values, it can sometimes look better to flip the bars sideways and put the legend on the bottom. This may impact how your factor levels are ordered - in this case we reverse them with fct_rev() to put missing and other at the bottom.\n\n# A) Outcomes in all cases\nggplot(linelist %>% drop_na(outcome)) + \n geom_bar(aes(y = fct_rev(hospital)), width = 0.7) +\n theme_minimal()+\n labs(title = \"A) Number of cases by hospital\",\n y = \"Hospital\")\n\n\n# B) Outcomes in all cases by hosptial\nggplot(linelist %>% drop_na(outcome)) + \n geom_bar(aes(y = fct_rev(hospital), fill = outcome), width = 0.7) +\n theme_minimal()+\n theme(legend.position = \"bottom\") +\n labs(title = \"B) Number of recovered and dead Ebola cases, by hospital\",\n y = \"Hospital\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\ngeom_col()\nUse geom_col() if you want bar height (or height of stacked bar components) to reflect pre-calculated values that exists in the data. Often, these are summary or “aggregated” counts, or proportions.\nProvide column assignments for both axes to geom_col(). Typically your x-axis column is discrete and your y-axis column is numeric.\nLet’s say we have this dataset outcomes:\n\n\n# A tibble: 2 × 3\n outcome n proportion\n <chr> <int> <dbl>\n1 Death 1022 56.2\n2 Recover 796 43.8\n\n\nBelow is code using geom_col for creating simple bar charts to show the distribution of Ebola patient outcomes. With geom_col, both x and y need to be specified. Here x is the categorical variable along the x axis, and y is the generated proportions column proportion.\n\n# Outcomes in all cases\nggplot(outcomes) + \n geom_col(aes(x=outcome, y = proportion)) +\n labs(subtitle = \"Number of recovered and dead Ebola cases\")\n\n\n\n\n\n\n\n\nTo show breakdowns by hospital, we would need our table to contain more information, and to be in “long” format. We create this table with the frequencies of the combined categories outcome and hospital (see Grouping data page for grouping tips).\n\noutcomes2 <- linelist %>% \n drop_na(outcome) %>% \n count(hospital, outcome) %>% # get counts by hospital and outcome\n group_by(hospital) %>% # Group so proportions are out of hospital total\n mutate(proportion = n/sum(n)*100) # calculate proportions of hospital total\n\nhead(outcomes2) # Preview data\n\n# A tibble: 6 × 4\n# Groups: hospital [3]\n hospital outcome n proportion\n <fct> <chr> <int> <dbl>\n1 St. Mark's Maternity Hospital (SMMH) Death 199 61.2\n2 St. Mark's Maternity Hospital (SMMH) Recover 126 38.8\n3 Port Hospital Death 785 57.6\n4 Port Hospital Recover 579 42.4\n5 Central Hospital Death 193 53.9\n6 Central Hospital Recover 165 46.1\n\n\nWe then create the ggplot with some added formatting:\n\nAxis flip: Swapped the axis around with coord_flip() so that we can read the hospital names.\nColumns side-by-side: Added a position = \"dodge\" argument so that the bars for death and recover are presented side by side rather than stacked. Note stacked bars are the default.\nColumn width: Specified ‘width’, so the columns are half as thin as the full possible width.\nColumn order: Reversed the order of the categories on the y axis so that ‘Other’ and ‘Missing’ are at the bottom, with scale_x_discrete(limits=rev). Note that we used that rather than scale_y_discrete because hospital is stated in the x argument of aes(), even if visually it is on the y axis. We do this because Ggplot seems to present categories backwards unless we tell it not to.\n\nOther details: Labels/titles and colours added within labs and scale_fill_color respectively.\n\n\n# Outcomes in all cases by hospital\nggplot(outcomes2) + \n geom_col(\n mapping = aes(\n x = proportion, # show pre-calculated proportion values\n y = fct_rev(hospital), # reverse level order so missing/other at bottom\n fill = outcome), # stacked by outcome\n width = 0.5)+ # thinner bars (out of 1)\n theme_minimal() + # Minimal theme \n theme(legend.position = \"bottom\")+\n labs(subtitle = \"Number of recovered and dead Ebola cases, by hospital\",\n fill = \"Outcome\", # legend title\n y = \"Count\", # y axis title\n x = \"Hospital of admission\")+ # x axis title\n scale_fill_manual( # adding colors manually\n values = c(\"Death\"= \"#3B1c8C\",\n \"Recover\" = \"#21908D\" )) \n\n\n\n\n\n\n\n\nNote that the proportions are binary, so we may prefer to drop ‘recover’ and just show the proportion who died. This is just for illustration purposes.\nIf using geom_col() with dates data (e.g. an epicurve from aggregated data) - you will want to adjust the width = argument to remove the “gap” lines between the bars. If using daily data set width = 1. If weekly, width = 7. Months are not possible because each month has a different number of days.\n\n\ngeom_histogram()\nHistograms may look like bar charts, but are distinct because they measure the distribution of a continuous variable. There are no spaces between the “bars”, and only one column is provided to geom_histogram(). There are arguments specific to histograms such as bin_width = and breaks = to specify how the data should be binned. The section above on continuous data and the page on Epidemic curves provide additional detail.", + "text": "30.13 Plot categorical data\nCategorical data can be character values, could be logical (TRUE/FALSE), or factors (see the Factors page).\n\nPreparation\n\nData structure\nThe first thing to understand about your categorical data is whether it exists as raw observations like a linelist of cases, or as a summary or aggregate data frame that holds counts or proportions. The state of your data will impact which plotting function you use:\n\nIf your data are raw observations with one row per observation, you will likely use geom_bar().\nIf your data are already aggregated into counts or proportions, you will likely use geom_col().\n\n\n\nColumn class and value ordering\nNext, examine the class of the columns you want to plot. We look at hospital, first with class() from base R, and with tabyl() from janitor.\n\n# View class of hospital column - we can see it is a character\nclass(linelist$hospital)\n\n[1] \"character\"\n\n# Look at values and proportions within hospital column\nlinelist %>% \n tabyl(hospital)\n\n hospital n percent\n Central Hospital 454 0.07710598\n Military Hospital 896 0.15217391\n Missing 1469 0.24949049\n Other 885 0.15030571\n Port Hospital 1762 0.29925272\n St. Mark's Maternity Hospital (SMMH) 422 0.07167120\n\n\nWe can see the values within are characters, as they are hospital names, and by default they are ordered alphabetically. There are ‘other’ and ‘missing’ values, which we would prefer to be the last subcategories when presenting breakdowns. So we change this column into a factor and re-order it. This is covered in more detail in the Factors page.\n\n# Convert to factor and define level order so \"Other\" and \"Missing\" are last\nlinelist <- linelist %>% \n mutate(\n hospital = fct_relevel(hospital, \n \"St. Mark's Maternity Hospital (SMMH)\",\n \"Port Hospital\", \n \"Central Hospital\",\n \"Military Hospital\",\n \"Other\",\n \"Missing\"))\n\n\nlevels(linelist$hospital)\n\n[1] \"St. Mark's Maternity Hospital (SMMH)\"\n[2] \"Port Hospital\" \n[3] \"Central Hospital\" \n[4] \"Military Hospital\" \n[5] \"Other\" \n[6] \"Missing\" \n\n\n\n\n\ngeom_bar()\nUse geom_bar() if you want bar height (or the height of stacked bar components) to reflect the number of relevant rows in the data. These bars will have gaps between them, unless the width = plot aesthetic is adjusted.\n\nProvide only one axis column assignment (typically x-axis). If you provide x and y, you will get Error: stat_count() can only have an x or y aesthetic.\n\nYou can create stacked bars by adding a fill = column assignment within mapping = aes().\n\nThe opposite axis will be titled “count” by default, because it represents the number of rows.\n\nBelow, we have assigned outcome to the y-axis, but it could just as easily be on the x-axis. If you have longer character values, it can sometimes look better to flip the bars sideways and put the legend on the bottom. This may impact how your factor levels are ordered - in this case we reverse them with fct_rev() to put missing and other at the bottom.\n\n# A) Outcomes in all cases\nggplot(linelist %>% drop_na(outcome)) + \n geom_bar(mapping = aes(y = fct_rev(hospital)), width = 0.7) +\n theme_minimal() +\n labs(title = \"A) Number of cases by hospital\",\n y = \"Hospital\")\n\n\n# B) Outcomes in all cases by hosptial\nggplot(linelist %>% drop_na(outcome)) + \n geom_bar(mapping = aes(y = fct_rev(hospital), fill = outcome), width = 0.7) +\n theme_minimal() +\n theme(legend.position = \"bottom\") +\n labs(title = \"B) Number of recovered and dead Ebola cases, by hospital\",\n y = \"Hospital\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\ngeom_col()\nUse geom_col() if you want bar height (or height of stacked bar components) to reflect pre-calculated values that exists in the data. Often, these are summary or “aggregated” counts, or proportions.\nProvide column assignments for both axes to geom_col(). Typically your x-axis column is discrete and your y-axis column is numeric.\nLet’s say we have this dataset outcomes:\n\n\n# A tibble: 2 × 3\n outcome n proportion\n <chr> <int> <dbl>\n1 Death 1022 56.2\n2 Recover 796 43.8\n\n\nBelow is code using geom_col for creating simple bar charts to show the distribution of Ebola patient outcomes. With geom_col, both x and y need to be specified. Here x is the categorical variable along the x axis, and y is the generated proportions column proportion.\n\n# Outcomes in all cases\nggplot(outcomes) + \n geom_col(mapping = aes(x=outcome, y = proportion)) +\n labs(subtitle = \"Number of recovered and dead Ebola cases\")\n\n\n\n\n\n\n\n\nTo show breakdowns by hospital, we would need our table to contain more information, and to be in “long” format. We create this table with the frequencies of the combined categories outcome and hospital (see Grouping data page for grouping tips).\n\noutcomes2 <- linelist %>% \n drop_na(outcome) %>% \n count(hospital, outcome) %>% # get counts by hospital and outcome\n group_by(hospital) %>% # Group so proportions are out of hospital total\n mutate(proportion = n/sum(n)*100) # calculate proportions of hospital total\n\nhead(outcomes2) # Preview data\n\n# A tibble: 6 × 4\n# Groups: hospital [3]\n hospital outcome n proportion\n <fct> <chr> <int> <dbl>\n1 St. Mark's Maternity Hospital (SMMH) Death 199 61.2\n2 St. Mark's Maternity Hospital (SMMH) Recover 126 38.8\n3 Port Hospital Death 785 57.6\n4 Port Hospital Recover 579 42.4\n5 Central Hospital Death 193 53.9\n6 Central Hospital Recover 165 46.1\n\n\nWe then create the ggplot with some added formatting:\n\nAxis flip: Swapped the axis around with coord_flip() so that we can read the hospital names.\nColumns side-by-side: Added a position = \"dodge\" argument so that the bars for death and recover are presented side by side rather than stacked. Note stacked bars are the default.\nColumn width: Specified ‘width’, so the columns are half as thin as the full possible width.\nColumn order: Reversed the order of the categories on the y axis so that ‘Other’ and ‘Missing’ are at the bottom, with scale_x_discrete(limits=rev). Note that we used that rather than scale_y_discrete because hospital is stated in the x argument of aes(), even if visually it is on the y axis. We do this because Ggplot seems to present categories backwards unless we tell it not to.\n\nOther details: Labels/titles and colours added within labs and scale_fill_color respectively.\n\n\n# Outcomes in all cases by hospital\nggplot(outcomes2) + \n geom_col(\n mapping = aes(\n x = proportion, # show pre-calculated proportion values\n y = fct_rev(hospital), # reverse level order so missing/other at bottom\n fill = outcome), # stacked by outcome\n width = 0.5) + # thinner bars (out of 1)\n theme_minimal() + # Minimal theme \n theme(legend.position = \"bottom\") +\n labs(subtitle = \"Proportio of recovered and dead Ebola cases, by hospital\",\n fill = \"Outcome\", # legend title\n x = \"Proportion\", # y axis title\n y = \"Hospital of admission\") + # x axis title\n scale_fill_manual( # adding colors manually\n values = c(\"Death\"= \"#3B1c8C\",\n \"Recover\" = \"#21908D\" )) \n\n\n\n\n\n\n\n\nNote that the proportions are binary, so we may prefer to drop ‘recover’ and just show the proportion who died. This is just for illustration purposes.\nIf using geom_col() with dates data (e.g. an epicurve from aggregated data) - you will want to adjust the width = argument to remove the “gap” lines between the bars. If using daily data set width = 1. If weekly, width = 7. Months are not possible because each month has a different number of days.\n\n\ngeom_histogram()\nHistograms may look like bar charts, but are distinct because they measure the distribution of a continuous variable. There are no spaces between the “bars”, and only one column is provided to geom_histogram(). There are arguments specific to histograms such as bin_width = and breaks = to specify how the data should be binned. The section above on continuous data and the page on Epidemic curves provide additional detail.", "crumbs": [ "Data Visualization", "30  ggplot basics" @@ -2815,7 +2815,7 @@ "href": "new_pages/ggplot_tips.html#preparation", "title": "31  ggplot tips", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # includes ggplot2 and other\n rio, # import/export\n here, # file locator\n stringr, # working with characters \n scales, # transform numbers\n ggrepel, # smartly-placed labels\n gghighlight, # highlight one part of plot\n RColorBrewer # color scales\n)\n\n\n\nImport data\nFor this page, we import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\nlinelist <- rio::import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # includes ggplot2 and other\n rio, # import/export\n here, # file locator\n stringr, # working with characters \n scales, # transform numbers\n cowplot, # for dual axes\n ggrepel, # smartly-placed labels\n gghighlight, # highlight one part of plot\n RColorBrewer # color scales\n)\n\n\n\nImport data\nFor this page, we import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\nlinelist <- rio::import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2826,7 +2826,7 @@ "href": "new_pages/ggplot_tips.html#ggplot_tips_colors", "title": "31  ggplot tips", "section": "31.2 Scales for color, fill, axes, etc.", - "text": "31.2 Scales for color, fill, axes, etc.\nIn ggplot2, when aesthetics of plotted data (e.g. size, color, shape, fill, plot axis) are mapped to columns in the data, the exact display can be adjusted with the corresponding “scale” command. In this section we explain some common scale adjustments.\n\n31.2.1 Color schemes\nOne thing that can initially be difficult to understand with ggplot2 is control of color schemes. Note that this section discusses the color of plot objects (geoms/shapes) such as points, bars, lines, tiles, etc. To adjust color of accessory text, titles, or background color see the Themes section of the [ggplot basics] page.\nTo control “color” of plot objects you will be adjusting either the color = aesthetic (the exterior color) or the fill = aesthetic (the interior color). One exception to this pattern is geom_point(), where you really only get to control color =, which controls the color of the point (interior and exterior).\nWhen setting colour or fill you can use colour names recognized by R like \"red\" (see complete list or enter ?colors), or a specific hex colour such as \"#ff0505\".\n\n# histogram - \nggplot(data = linelist, mapping = aes(x = age))+ # set data and axes\n geom_histogram( # display histogram\n binwidth = 7, # width of bins\n color = \"red\", # bin line color\n fill = \"lightblue\") # bin interior color (fill) \n\n\n\n\n\n\n\n\nAs explained the ggplot basics section on mapping data to the plot, aesthetics such as fill = and color = can be defined either outside of a mapping = aes() statement or inside of one. If outside the aes(), the assigned value should be static (e.g. color = \"blue\") and will apply for all data plotted by the geom. If inside, the aesthetic should be mapped to a column, like color = hospital, and the expression will vary by the value for that row in the data. A few examples:\n\n# Static color for points and for line\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(color = \"purple\")+\n geom_vline(xintercept = 50, color = \"orange\")+\n labs(title = \"Static color for points and line\")\n\n# Color mapped to continuous column\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(mapping = aes(color = temp))+ \n labs(title = \"Color mapped to continuous column\")\n\n# Color mapped to discrete column\nggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ \n geom_point(mapping = aes(color = gender))+ \n labs(title = \"Color mapped to discrete column\")\n\n# bar plot, fill to discrete column, color to static value\nggplot(data = linelist, mapping = aes(x = hospital))+ \n geom_bar(mapping = aes(fill = gender), color = \"yellow\")+ \n labs(title = \"Fill mapped to discrete column, static color\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nScales\nOnce you map a column to a plot aesthetic (e.g. x =, y =, fill =, color =…), your plot will gain a scale/legend. See above how the scale can be continuous, discrete, date, etc. values depending on the class of the assigned column. If you have multiple aesthetics mapped to columns, your plot will have multiple scales.\nYou can control the scales with the appropriate scales_() function. The scale functions of ggplot() have 3 parts that are written like this: scale_AESTHETIC_METHOD().\n\nThe first part, scale_(), is fixed.\n\nThe second part, the AESTHETIC, should be the aesthetic that you want to adjust the scale for (_fill_, _shape_, _color_, _size_, _alpha_…) - the options here also include _x_ and _y_.\n\nThe third part, the METHOD, will be either _discrete(), continuous(), _date(), _gradient(), or _manual() depending on the class of the column and how you want to control it. There are others, but these are the most-often used.\n\nBe sure that you use the correct function for the scale! Otherwise your scale command will not appear to change anything. If you have multiple scales, you may use multiple scale functions to adjust them! For example:\n\n\nScale arguments\nEach kind of scale has its own arguments, though there is some overlap. Query the function like ?scale_color_discrete in the R console to see the function argument documentation.\nFor continuous scales, use breaks = to provide a sequence of values with seq() (take to =, from =, and by = as shown in the example below. Set expand = c(0,0) to eliminate padding space around the axes (this can be used on any _x_ or _y_ scale.\nFor discrete scales, you can adjust the order of level appearance with breaks =, and how the values display with the labels = argument. Provide a character vector to each of those (see example below). You can also drop NA easily by setting na.translate = FALSE.\nThe nuances of date scales are covered more extensively in the Epidemic curves page.\n\n\nManual adjustments\nOne of the most useful tricks is using “manual” scaling functions to explicitly assign colors as you desire. These are functions with the syntax scale_xxx_manual() (e.g. scale_colour_manual() or scale_fill_manual()). Each of the below arguments are demonstrated in the code example below.\n\nAssign colors to data values with the values = argument\n\nSpecify a color for NA with na.value =\n\nChange how the values are written in the legend with the labels = argument\n\nChange the legend title with name =\n\nBelow, we create a bar plot and show how it appears by default, and then with three scales adjusted - the continuous y-axis scale, the discrete x-axis scale, and manual adjustment of the fill (interior bar color).\n\n# BASELINE - no scale adjustment\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = outcome, fill = gender))+\n labs(title = \"Baseline - no scale adjustments\")\n\n\n\n\n\n\n\n# SCALES ADJUSTED\nggplot(data = linelist)+\n \n geom_bar(mapping = aes(x = outcome, fill = gender), color = \"black\")+\n \n theme_minimal()+ # simplify background\n \n scale_y_continuous( # continuous scale for y-axis (counts)\n expand = c(0,0), # no padding\n breaks = seq(from = 0,\n to = 3000,\n by = 500))+\n \n scale_x_discrete( # discrete scale for x-axis (gender)\n expand = c(0,0), # no padding\n drop = FALSE, # show all factor levels (even if not in data)\n na.translate = FALSE, # remove NA outcomes from plot\n labels = c(\"Died\", \"Recovered\"))+ # Change display of values\n \n \n scale_fill_manual( # Manually specify fill (bar interior color)\n values = c(\"m\" = \"violetred\", # reference values in data to assign colors\n \"f\" = \"aquamarine\"),\n labels = c(\"m\" = \"Male\", # re-label the legend (use \"=\" assignment to avoid mistakes)\n \"f\" = \"Female\",\n \"Missing\"),\n name = \"Gender\", # title of legend\n na.value = \"grey\" # assign a color for missing values\n )+\n labs(title = \"Adjustment of scales\") # Adjust the title of the fill legend\n\n\n\n\n\n\n\n\n\n\nContinuous axes scales\nWhen data are mapping to the plot axes, these too can be adjusted with scales commands. A common example is adjusting the display of an axis (e.g. y-axis) that is mapped to a column with continuous data.\nWe may want to adjust the breaks or display of the values in the ggplot using scale_y_continuous(). As noted above, use the argument breaks = to provide a sequence of values that will serve as “breaks” along the scale. These are the values at which numbers will display. To this argument, you can provide a c() vector containing the desired break values, or you can provide a regular sequence of numbers using the base R function seq(). This seq() function accepts to =, from =, and by =.\n\n# BASELINE - no scale adjustment\nggplot(data = linelist)+\n geom_bar(mapping = aes(x = outcome, fill = gender))+\n labs(title = \"Baseline - no scale adjustments\")\n\n# \nggplot(data = linelist)+\n geom_bar(mapping = aes(x = outcome, fill = gender))+\n scale_y_continuous(\n breaks = seq(\n from = 0,\n to = 3000,\n by = 100)\n )+\n labs(title = \"Adjusted y-axis breaks\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nDisplay percents\nIf your original data values are proportions, you can easily display them as percents with “%” by providing labels = scales::percent in your scales command, as shown below.\nWhile an alternative would be to convert the values to character and add a “%” character to the end, this approach will cause complications because your data will no longer be continuous numeric values.\n\n# Original y-axis proportions\n#############################\nlinelist %>% # start with linelist\n group_by(hospital) %>% # group data by hospital\n summarise( # create summary columns\n n = n(), # total number of rows in group\n deaths = sum(outcome == \"Death\", na.rm=T), # number of deaths in group\n prop_death = deaths/n) %>% # proportion deaths in group\n ggplot( # begin plotting\n mapping = aes(\n x = hospital,\n y = prop_death))+ \n geom_col()+\n theme_minimal()+\n labs(title = \"Display y-axis original proportions\")\n\n\n\n# Display y-axis proportions as percents\n########################################\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n n = n(),\n deaths = sum(outcome == \"Death\", na.rm=T),\n prop_death = deaths/n) %>% \n ggplot(\n mapping = aes(\n x = hospital,\n y = prop_death))+\n geom_col()+\n theme_minimal()+\n labs(title = \"Display y-axis as percents (%)\")+\n scale_y_continuous(\n labels = scales::percent # display proportions as percents\n )\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLog scale\nTo transform a continuous axis to log scale, add trans = \"log2\" to the scale command. For purposes of example, we create a data frame of regions with their respective preparedness_index and cumulative cases values.\n\nplot_data <- data.frame(\n region = c(\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\"),\n preparedness_index = c(8.8, 7.5, 3.4, 3.6, 2.1, 7.9, 7.0, 5.6, 1.0),\n cases_cumulative = c(15, 45, 80, 20, 21, 7, 51, 30, 1442)\n)\n\nplot_data\n\n region preparedness_index cases_cumulative\n1 A 8.8 15\n2 B 7.5 45\n3 C 3.4 80\n4 D 3.6 20\n5 E 2.1 21\n6 F 7.9 7\n7 G 7.0 51\n8 H 5.6 30\n9 I 1.0 1442\n\n\nThe cumulative cases for region “I” are dramatically greater than all the other regions. In circumstances like this, you may elect to display the y-axis using a log scale so the reader can see differences between the regions with fewer cumulative cases.\n\n# Original y-axis\npreparedness_plot <- ggplot(data = plot_data, \n mapping = aes(\n x = preparedness_index,\n y = cases_cumulative))+\n geom_point(size = 2)+ # points for each region \n geom_text(\n mapping = aes(label = region),\n vjust = 1.5)+ # add text labels\n theme_minimal()\n\npreparedness_plot # print original plot\n\n\n# print with y-axis transformed\npreparedness_plot+ # begin with plot saved above\n scale_y_continuous(trans = \"log2\") # add transformation for y-axis\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nGradient scales\nFill gradient scales can involve additional nuance. The defaults are usually quite pleasing, but you may want to adjust the values, cutoffs, etc.\nTo demonstrate how to adjust a continuous color scale, we’ll use a data set from the Contact tracing page that contains the ages of cases and of their source cases.\n\ncase_source_relationships <- rio::import(here::here(\"data\", \"godata\", \"relationships_clean.rds\")) %>% \n select(source_age, target_age) \n\nBelow, we produce a “raster” heat tile density plot. We won’t elaborate how (see the link in paragraph above) but we will focus on how we can adjust the color scale. Read more about the stat_density2d() ggplot2 function here. Note how the fill scale is continuous.\n\ntrans_matrix <- ggplot(\n data = case_source_relationships,\n mapping = aes(x = source_age, y = target_age))+\n stat_density2d(\n geom = \"raster\",\n mapping = aes(fill = after_stat(density)),\n contour = FALSE)+\n theme_minimal()\n\nNow we show some variations on the fill scale:\n\ntrans_matrix\ntrans_matrix + scale_fill_viridis_c(option = \"plasma\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNow we show some examples of actually adjusting the break points of the scale:\n\nscale_fill_gradient() accepts two colors (high/low)\n\nscale_fill_gradientn() accepts a vector of any length of colors to values = (intermediate values will be interpolated)\n\nUse scales::rescale() to adjust how colors are positioned along the gradient; it rescales your vector of positions to be between 0 and 1.\n\n\ntrans_matrix + \n scale_fill_gradient( # 2-sided gradient scale\n low = \"aquamarine\", # low value\n high = \"purple\", # high value\n na.value = \"grey\", # value for NA\n name = \"Density\")+ # Legend title\n labs(title = \"Manually specify high/low colors\")\n\n# 3+ colors to scale\ntrans_matrix + \n scale_fill_gradientn( # 3-color scale (low/mid/high)\n colors = c(\"blue\", \"yellow\",\"red\") # provide colors in vector\n )+\n labs(title = \"3-color scale\")\n\n# Use of rescale() to adjust placement of colors along scale\ntrans_matrix + \n scale_fill_gradientn( # provide any number of colors\n colors = c(\"blue\", \"yellow\",\"red\", \"black\"),\n values = scales::rescale(c(0, 0.05, 0.07, 0.10, 0.15, 0.20, 0.3, 0.5)) # positions for colors are rescaled between 0 and 1\n )+\n labs(title = \"Colors not evenly positioned\")\n\n# use of limits to cut-off values that get fill color\ntrans_matrix + \n scale_fill_gradientn( \n colors = c(\"blue\", \"yellow\",\"red\"),\n limits = c(0, 0.0002))+\n labs(title = \"Restrict value limits, resulting in grey space\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPalettes\n\nColorbrewer and Viridis\nMore generally, if you want predefined palettes, you can use the scale_xxx_brewer or scale_xxx_viridis_y functions.\nThe ‘brewer’ functions can draw from colorbrewer.org palettes.\nThe ‘viridis’ functions draw from viridis (colourblind friendly!) palettes, which “provide colour maps that are perceptually uniform in both colour and black-and-white. They are also designed to be perceived by viewers with common forms of colour blindness.” (read more here and here). Define if the palette is discrete, continuous, or binned by specifying this at the end of the function (e.g. discrete is scale_xxx_viridis_d).\nIt is advised that you test your plot in this color blindness simulator. If you have a red/green color scheme, try a “hot-cold” (red-blue) scheme instead as described here\nHere is an example from the ggplot basics page, using various color schemes.\n\nsymp_plot <- linelist %>% # begin with linelist\n select(c(case_id, fever, chills, cough, aches, vomit)) %>% # select columns\n pivot_longer( # pivot longer\n cols = -case_id, \n names_to = \"symptom_name\",\n values_to = \"symptom_is_present\") %>%\n mutate( # replace missing values\n symptom_is_present = replace_na(symptom_is_present, \"unknown\")) %>% \n ggplot( # begin ggplot!\n mapping = aes(x = symptom_name, fill = symptom_is_present))+\n geom_bar(position = \"fill\", col = \"black\") + \n theme_classic() +\n theme(legend.position = \"bottom\")+\n labs(\n x = \"Symptom\",\n y = \"Symptom status (proportion)\"\n )\n\nsymp_plot # print with default colors\n\n#################################\n# print with manually-specified colors\nsymp_plot +\n scale_fill_manual(\n values = c(\"yes\" = \"black\", # explicitly define colours\n \"no\" = \"white\",\n \"unknown\" = \"grey\"),\n breaks = c(\"yes\", \"no\", \"unknown\"), # order the factors correctly\n name = \"\" # set legend to no title\n\n ) \n\n#################################\n# print with viridis discrete colors\nsymp_plot +\n scale_fill_viridis_d(\n breaks = c(\"yes\", \"no\", \"unknown\"),\n name = \"\"\n )", + "text": "31.2 Scales for color, fill, axes, etc.\nIn ggplot2, when aesthetics of plotted data (e.g. size, color, shape, fill, plot axis) are mapped to columns in the data, the exact display can be adjusted with the corresponding “scale” command. In this section we explain some common scale adjustments.\n\n31.2.1 Color schemes\nOne thing that can initially be difficult to understand with ggplot2 is control of color schemes. Note that this section discusses the color of plot objects (geoms/shapes) such as points, bars, lines, tiles, etc. To adjust color of accessory text, titles, or background color see the Themes section of the ggplot basics page.\nTo control “color” of plot objects you will be adjusting either the color = aesthetic (the exterior color) or the fill = aesthetic (the interior color). One exception to this pattern is geom_point(), where you really only get to control color =, which controls the color of the point (interior and exterior).\nWhen setting colour or fill you can use colour names recognized by R like \"red\" (see complete list or enter ?colors), or a specific hex colour such as \"#ff0505\".\n\n# histogram - \nggplot(data = linelist, \n mapping = aes(x = age)) + # set data and axes\n geom_histogram( # display histogram\n binwidth = 7, # width of bins\n color = \"red\", # bin line color\n fill = \"lightblue\") # bin interior color (fill) \n\n\n\n\n\n\n\n\nAs explained the ggplot basics section on mapping data to the plot, aesthetics such as fill = and color = can be defined either outside of a mapping = aes() statement, or inside of one. If outside the aes(), the assigned value should be static (e.g. color = \"blue\") and will apply for all data plotted by the geom. If inside, the aesthetic should be mapped to a column, like color = hospital, and the expression will vary by the value for that row in the data. A few examples:\n\n# Static color for points and for line\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(color = \"purple\") +\n geom_vline(xintercept = 50, color = \"orange\") +\n labs(title = \"Static color for points and line\")\n\n# Color mapped to continuous column\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(mapping = aes(color = temp)) + \n labs(title = \"Color mapped to continuous column\")\n\n# Color mapped to discrete column\nggplot(data = linelist, \n mapping = aes(x = age, y = wt_kg)) + \n geom_point(mapping = aes(color = gender)) + \n labs(title = \"Color mapped to discrete column\")\n\n# bar plot, fill to discrete column, color to static value\nggplot(data = linelist, \n mapping = aes(y = hospital)) + \n geom_bar(mapping = aes(fill = gender), color = \"yellow\") + \n labs(title = \"Fill mapped to discrete column, static color\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nScales\nOnce you map a column to a plot aesthetic (e.g. x =, y =, fill =, color =…), your plot will gain a scale/legend. See above how the scale can be continuous, discrete, date, etc. values depending on the class of the assigned column. If you have multiple aesthetics mapped to columns, your plot will have multiple scales.\nYou can control the scales with the appropriate scales_() function. The scale functions of ggplot() have 3 parts that are written like this: scale_AESTHETIC_METHOD().\n\nThe first part, scale_(), is fixed.\n\nThe second part, the AESTHETIC, should be the aesthetic that you want to adjust the scale for (_fill_, _shape_, _color_, _size_, _alpha_…) - the options here also include _x_ and _y_\nThe third part, the METHOD, will be either _discrete(), continuous(), _date(), _gradient(), or _manual() depending on the class of the column and how you want to control it. There are others, but these are the most-often used\n\nBe sure that you use the correct function for the scale! Otherwise your scale command will not appear to change anything. If you have multiple scales, you may use multiple scale functions to adjust them! For example:\n\n\nScale arguments\nEach kind of scale has its own arguments, though there is some overlap. Query the function like ?scale_color_discrete in the R console to see the function argument documentation.\nFor continuous scales, use breaks = to provide a sequence of values with seq() (take to =, from =, and by = as shown in the example below. Set expand = c(0,0) to eliminate padding space around the axes (this can be used on any _x_ or _y_ scale).\nFor discrete scales, you can adjust the order of level appearance with breaks =, and how the values display with the labels = argument. Provide a character vector to each of those (see example below). You can also drop NA easily by setting na.translate = FALSE.\nThe nuances of date scales are covered more extensively in the Epidemic curves page.\n\n\nManual adjustments\nOne of the most useful tricks is using “manual” scaling functions to explicitly assign colors as you desire. These are functions with the syntax scale_xxx_manual() (e.g. scale_colour_manual() or scale_fill_manual()). Each of the below arguments are demonstrated in the code example below.\n\nAssign colors to data values with the values = argument\nSpecify a color for NA with na.value =\nChange how the values are written in the legend with the labels = argument\nChange the legend title with name =\n\nBelow, we create a bar plot and show how it appears by default, and then with three scales adjusted - the continuous y-axis scale, the discrete x-axis scale, and manual adjustment of the fill (interior bar color).\n\n# BASELINE - no scale adjustment\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = outcome, fill = gender)) +\n labs(title = \"Baseline - no scale adjustments\")\n\n\n\n\n\n\n\n# SCALES ADJUSTED\nggplot(data = linelist) +\n \n geom_bar(mapping = aes(x = outcome, fill = gender), color = \"black\") +\n \n theme_minimal() + # simplify background\n \n scale_y_continuous( # continuous scale for y-axis (counts)\n expand = c(0,0), # no padding\n breaks = seq(from = 0,\n to = 3000,\n by = 500)) +\n \n scale_x_discrete( # discrete scale for x-axis (gender)\n expand = c(0,0), # no padding\n drop = FALSE, # show all factor levels (even if not in data)\n na.translate = FALSE, # remove NA outcomes from plot\n labels = c(\"Died\", \"Recovered\")) + # Change display of values\n \n \n scale_fill_manual( # Manually specify fill (bar interior color)\n values = c(\"m\" = \"violetred\", # reference values in data to assign colors\n \"f\" = \"aquamarine\"),\n labels = c(\"m\" = \"Male\", # re-label the legend (use \"=\" assignment to avoid mistakes)\n \"f\" = \"Female\",\n \"Missing\"),\n name = \"Gender\", # title of legend\n na.value = \"grey\" # assign a color for missing values\n ) +\n labs(title = \"Adjustment of scales\") # Adjust the title of the fill legend\n\n\n\n\n\n\n\n\n\n\nContinuous axes scales\nWhen data are mapping to the plot axes, these too can be adjusted with scales commands. A common example is adjusting the display of an axis (e.g. y-axis) that is mapped to a column with continuous data.\nWe may want to adjust the breaks or display of the values in the ggplot using scale_y_continuous(). As noted above, use the argument breaks = to provide a sequence of values that will serve as “breaks” along the scale. These are the values at which numbers will display. To this argument, you can provide a c() vector containing the desired break values, or you can provide a regular sequence of numbers using the base R function seq(). This seq() function accepts to =, from =, and by =.\n\n# BASELINE - no scale adjustment\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = outcome, fill = gender)) +\n labs(title = \"Baseline - no scale adjustments\")\n\n# Updated - scale adjustment\nggplot(data = linelist) +\n geom_bar(mapping = aes(x = outcome, fill = gender)) +\n scale_y_continuous(\n breaks = seq(\n from = 0,\n to = 3000,\n by = 100)\n ) +\n labs(title = \"Adjusted y-axis breaks\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nDisplay percents\nIf your original data values are proportions, you can easily display them as percents with “%” by providing labels = scales::percent in your scales command, as shown below.\nWhile an alternative would be to convert the values to character and add a “%” character to the end, this approach will cause complications because your data will no longer be continuous numeric values.\n\n# Original y-axis proportions\n#############################\nlinelist %>% # start with linelist\n group_by(hospital) %>% # group data by hospital\n summarise( # create summary columns\n n = n(), # total number of rows in group\n deaths = sum(outcome == \"Death\", na.rm=T), # number of deaths in group\n prop_death = deaths/n) %>% # proportion deaths in group\n ggplot( # begin plotting\n mapping = aes(\n x = hospital,\n y = prop_death)) + \n geom_col() +\n theme_minimal() +\n labs(title = \"Display y-axis original proportions\")\n\n\n\n# Display y-axis proportions as percents\n########################################\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n n = n(),\n deaths = sum(outcome == \"Death\", na.rm=T),\n prop_death = deaths/n) %>% \n ggplot(\n mapping = aes(\n x = hospital,\n y = prop_death)) +\n geom_col() +\n theme_minimal() +\n labs(title = \"Display y-axis as percents (%)\") +\n scale_y_continuous(\n labels = scales::percent # display proportions as percents\n )\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLog scale\nTo transform a continuous axis to log scale, add trans = \"log2\" to the scale command. For purposes of example, we create a data frame of regions with their respective preparedness_index and cumulative cases values.\n\nplot_data <- data.frame(\n region = c(\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\"),\n preparedness_index = c(8.8, 7.5, 3.4, 3.6, 2.1, 7.9, 7.0, 5.6, 1.0),\n cases_cumulative = c(15, 45, 80, 20, 21, 7, 51, 30, 1442)\n)\n\nplot_data\n\n region preparedness_index cases_cumulative\n1 A 8.8 15\n2 B 7.5 45\n3 C 3.4 80\n4 D 3.6 20\n5 E 2.1 21\n6 F 7.9 7\n7 G 7.0 51\n8 H 5.6 30\n9 I 1.0 1442\n\n\nThe cumulative cases for region “I” are dramatically greater than all the other regions. In circumstances like this, you may elect to display the y-axis using a log scale so the reader can see differences between the regions with fewer cumulative cases.\n\n# Original y-axis\npreparedness_plot <- ggplot(data = plot_data, \n mapping = aes(\n x = preparedness_index,\n y = cases_cumulative)) +\n geom_point(size = 2) + # points for each region \n geom_text(\n mapping = aes(label = region),\n vjust = 1.5) + # add text labels\n theme_minimal()\n\npreparedness_plot # print original plot\n\n\n# print with y-axis transformed\npreparedness_plot+ # begin with plot saved above\n scale_y_continuous(trans = \"log2\") # add transformation for y-axis\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nGradient scales\nFill gradient scales can involve additional nuance. The defaults are usually quite pleasing, but you may want to adjust the values, cutoffs, etc.\nTo demonstrate how to adjust a continuous color scale, we’ll use a data set from the Contact tracing page that contains the ages of cases and of their source cases.\n\ncase_source_relationships <- rio::import(here::here(\"data\", \"godata\", \"relationships_clean.rds\")) %>% \n select(source_age, target_age) \n\nBelow, we produce a “raster” heat tile density plot. We won’t elaborate how (see the link in paragraph above) but we will focus on how we can adjust the color scale. Read more about the stat_density2d() ggplot2 function here. Note how the fill scale is continuous.\n\ntrans_matrix <- ggplot(\n data = case_source_relationships,\n mapping = aes(x = source_age, y = target_age)) +\n stat_density2d(\n geom = \"raster\",\n mapping = aes(fill = after_stat(density)),\n contour = FALSE) +\n theme_minimal()\n\nNow we show some variations on the fill scale:\n\ntrans_matrix\ntrans_matrix + scale_fill_viridis_c(option = \"plasma\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nNow we show some examples of actually adjusting the break points of the scale:\n\nscale_fill_gradient() accepts two colors (high/low)\nscale_fill_gradientn() accepts a vector of any length of colors to values = (intermediate values will be interpolated)\nUse scales::rescale() to adjust how colors are positioned along the gradient; it rescales your vector of positions to be between 0 and 1\n\n\ntrans_matrix + \n scale_fill_gradient( # 2-sided gradient scale\n low = \"aquamarine\", # low value\n high = \"purple\", # high value\n na.value = \"grey\", # value for NA\n name = \"Density\") + # Legend title\n labs(title = \"Manually specify high/low colors\")\n\n# 3+ colors to scale\ntrans_matrix + \n scale_fill_gradientn( # 3-color scale (low/mid/high)\n colors = c(\"blue\", \"yellow\",\"red\") # provide colors in vector\n ) +\n labs(title = \"3-color scale\")\n\n# Use of rescale() to adjust placement of colors along scale\ntrans_matrix + \n scale_fill_gradientn( # provide any number of colors\n colors = c(\"blue\", \"yellow\",\"red\", \"black\"),\n values = scales::rescale(c(0, 0.05, 0.07, 0.10, 0.15, 0.20, 0.3, 0.5)) # positions for colors are rescaled between 0 and 1\n ) +\n labs(title = \"Colors not evenly positioned\")\n\n# use of limits to cut-off values that get fill color\ntrans_matrix + \n scale_fill_gradientn( \n colors = c(\"blue\", \"yellow\",\"red\"),\n limits = c(0, 0.0002)) +\n labs(title = \"Restrict value limits, resulting in grey space\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPalettes\n\nColorbrewer and Viridis\nMore generally, if you want predefined palettes, you can use the scale_xxx_brewer or scale_xxx_viridis_y functions.\nThe ‘brewer’ functions can draw from colorbrewer2.org palettes.\nThe ‘viridis’ functions draw from viridis (colourblind friendly!) palettes, which “provide colour maps that are perceptually uniform in both colour and black-and-white. They are also designed to be perceived by viewers with common forms of colour blindness.” (read more here and here). Define if the palette is discrete, continuous, or binned by specifying this at the end of the function (e.g. discrete is scale_xxx_viridis_d).\nIt is advised that you test your plot in this color blindness simulator. If you have a red/green color scheme, try a “hot-cold” (red-blue) scheme instead as described here\nHere is an example from the ggplot basics page, using various color schemes.\n\nsymp_plot <- linelist %>% # begin with linelist\n select(c(case_id, fever, chills, cough, aches, vomit)) %>% # select columns\n pivot_longer( # pivot longer\n cols = -case_id, \n names_to = \"symptom_name\",\n values_to = \"symptom_is_present\") %>%\n mutate( # replace missing values\n symptom_is_present = replace_na(symptom_is_present, \"unknown\")) %>% \n ggplot( # begin ggplot!\n mapping = aes(x = symptom_name, fill = symptom_is_present)) +\n geom_bar(position = \"fill\", col = \"black\") + \n theme_classic() +\n theme(legend.position = \"bottom\") +\n labs(\n x = \"Symptom\",\n y = \"Symptom status (proportion)\"\n )\n\nsymp_plot # print with default colors\n\n#################################\n# print with manually-specified colors\nsymp_plot +\n scale_fill_manual(\n values = c(\"yes\" = \"black\", # explicitly define colours\n \"no\" = \"white\",\n \"unknown\" = \"grey\"),\n breaks = c(\"yes\", \"no\", \"unknown\"), # order the factors correctly\n name = \"\" # set legend to no title\n\n ) \n\n#################################\n# print with viridis discrete colors\nsymp_plot +\n scale_fill_viridis_d(\n breaks = c(\"yes\", \"no\", \"unknown\"),\n name = \"\"\n )", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2837,7 +2837,7 @@ "href": "new_pages/ggplot_tips.html#change-order-of-discrete-variables", "title": "31  ggplot tips", "section": "31.3 Change order of discrete variables", - "text": "31.3 Change order of discrete variables\nChanging the order that discrete variables appear in is often difficult to understand for people who are new to ggplot2 graphs. It’s easy to understand how to do this however once you understand how ggplot2 handles discrete variables under the hood. Generally speaking, if a discrete varaible is used, it is automatically converted to a factor type - which orders factors by alphabetical order by default. To handle this, you simply have to reorder the factor levels to reflect the order you would like them to appear in the chart. For more detailed information on how to reorder factor objects, see the factor section of the guide.\nWe can look at a common example using age groups - by default the 5-9 age group will be placed in the middle of the age groups (given alphanumeric order), but we can move it behind the 0-4 age group of the chart by releveling the factors.\n\nggplot(\n data = linelist %>% drop_na(age_cat5), # remove rows where age_cat5 is missing\n mapping = aes(x = fct_relevel(age_cat5, \"5-9\", after = 1))) + # relevel factor\n\n geom_bar() +\n \n labs(x = \"Age group\", y = \"Number of hospitalisations\",\n title = \"Total hospitalisations by age group\") +\n \n theme_minimal()\n\n\n\n\n\n\n\n\n\n31.3.0.1 ggthemr\nAlso consider using the ggthemr package. You can download this package from Github using the instructions here. It offers palettes that are very aesthetically pleasing, but be aware that these typically have a maximum number of values that can be limiting if you want more than 7 or 8 colors.", + "text": "31.3 Change order of discrete variables\nChanging the order that discrete variables appear in is often difficult to understand for people who are new to ggplot2 graphs. It’s easy to understand how to do this however once you understand how ggplot2 handles discrete variables under the hood. Generally speaking, if a discrete varaible is used, it is automatically converted to a factor type - which orders factors by alphabetical order by default. To handle this, you simply have to reorder the factor levels to reflect the order you would like them to appear in the chart. For more detailed information on how to reorder factor objects, see the factor section of the guide.\nWe can look at a common example using age groups - by default the 5-9 age group will be placed in the middle of the age groups (given alphanumeric order), but we can move it behind the 0-4 age group of the chart by releveling the factors.\n\nggplot(\n data = linelist %>% drop_na(age_cat5), # remove rows where age_cat5 is missing\n mapping = aes(x = fct_relevel(age_cat5, \"5-9\", after = 1))) + # relevel factor\n\n geom_bar() +\n \n labs(x = \"Age group\", y = \"Number of hospitalisations\",\n title = \"Total hospitalisations by age group\") +\n \n theme_minimal()\n\n\n\n\n\n\n\n\n\n31.3.0.1 Other color palette packages\nOne great thing about R is the wealth of packages out there. This is no different for color palettes. For example:\n\nThe ggsci package, which contains a wealth of scientific journal, and scifi tv show, color schemes\nThe wesanderson package which lists a series of color schemes around the films of Wes Anderson\nThe ggthemr package. You can download this package from Github using the instructions here. It offers palettes that are very aesthetically pleasing, but be aware that these typically have a maximum number of values that can be limiting if you want more than 7 or 8 colors\n\nThese are just a few examples, there are many more out there for you to find and try out!", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2848,7 +2848,7 @@ "href": "new_pages/ggplot_tips.html#contour-lines", "title": "31  ggplot tips", "section": "31.4 Contour lines", - "text": "31.4 Contour lines\nContour plots are helpful when you have many points that might cover each other (“overplotting”). The case-source data used above are again plotted, but more simply using stat_density2d() and stat_density2d_filled() to produce discrete contour levels - like a topographical map. Read more about the statistics here.\n\ncase_source_relationships %>% \n ggplot(aes(x = source_age, y = target_age))+\n stat_density2d()+\n geom_point()+\n theme_minimal()+\n labs(title = \"stat_density2d() + geom_point()\")\n\n\ncase_source_relationships %>% \n ggplot(aes(x = source_age, y = target_age))+\n stat_density2d_filled()+\n theme_minimal()+\n labs(title = \"stat_density2d_filled()\")", + "text": "31.4 Contour lines\nContour plots are helpful when you have many points that might cover each other (“overplotting”). The case-source data used above are again plotted, but more simply using stat_density2d() and stat_density2d_filled() to produce discrete contour levels - like a topographical map. Read more about the statistics here.\n\ncase_source_relationships %>% \n ggplot(mapping = aes(x = source_age, y = target_age)) +\n stat_density2d() +\n geom_point() +\n theme_minimal() +\n labs(title = \"stat_density2d() + geom_point()\")\n\n\ncase_source_relationships %>% \n ggplot(mapping = aes(x = source_age, y = target_age)) +\n stat_density2d_filled() +\n theme_minimal() +\n labs(title = \"stat_density2d_filled()\")", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2859,7 +2859,7 @@ "href": "new_pages/ggplot_tips.html#marginal-distributions", "title": "31  ggplot tips", "section": "31.5 Marginal distributions", - "text": "31.5 Marginal distributions\nTo show the distributions on the edges of a geom_point() scatterplot, you can use the ggExtra package and its function ggMarginal(). Save your original ggplot as an object, then pass it to ggMarginal() as shown below. Here are the key arguments:\n\nYou must specify the type = as either “histogram”, “density” “boxplot”, “violin”, or “densigram”.\n\nBy default, marginal plots will appear for both axes. You can set margins = to “x” or “y” if you only want one.\n\nOther optional arguments include fill = (bar color), color = (line color), size = (plot size relative to margin size, so larger number makes the marginal plot smaller).\n\nYou can provide other axis-specific arguments to xparams = and yparams =. For example, to have different histogram bin sizes, as shown below.\n\nYou can have the marginal plots reflect groups (columns that have been assigned to color = in your ggplot() mapped aesthetics). If this is the case, set the ggMarginal() argument groupColour = or groupFill = to TRUE, as shown below.\nRead more at this vignette, in the R Graph Gallery or the function R documentation ?ggMarginal.\n\n# Install/load ggExtra\npacman::p_load(ggExtra)\n\n# Basic scatter plot of weight and age\nscatter_plot <- ggplot(data = linelist)+\n geom_point(mapping = aes(y = wt_kg, x = age)) +\n labs(title = \"Scatter plot of weight and age\")\n\nTo add marginal histograms use type = \"histogram\". You can optionally set groupFill = TRUE to get stacked histograms.\n\n# with histograms\nggMarginal(\n scatter_plot, # add marginal histograms\n type = \"histogram\", # specify histograms\n fill = \"lightblue\", # bar fill\n xparams = list(binwidth = 10), # other parameters for x-axis marginal\n yparams = list(binwidth = 5)) # other parameters for y-axis marginal\n\n\n\n\n\n\n\n\nMarginal density plot with grouped/colored values:\n\n# Scatter plot, colored by outcome\n# Outcome column is assigned as color in ggplot. groupFill in ggMarginal set to TRUE\nscatter_plot_color <- ggplot(data = linelist %>% drop_na(gender))+\n geom_point(mapping = aes(y = wt_kg, x = age, color = gender)) +\n labs(title = \"Scatter plot of weight and age\")+\n theme(legend.position = \"bottom\")\n\nggMarginal(scatter_plot_color, type = \"density\", groupFill = TRUE)\n\n\n\n\n\n\n\n\nSet the size = arguemnt to adjust the relative size of the marginal plot. Smaller number makes a larger marginal plot. You also set color =. Below are is a marginal boxplot, with demonstration of the margins = argument so it appears on only one axis:\n\n# with boxplot \nggMarginal(\n scatter_plot,\n margins = \"x\", # only show x-axis marginal plot\n type = \"boxplot\")", + "text": "31.5 Marginal distributions\nTo show the distributions on the edges of a geom_point() scatterplot, you can use the ggExtra package and its function ggMarginal(). Save your original ggplot as an object, then pass it to ggMarginal() as shown below. Here are the key arguments:\n\nYou must specify the type = as either “histogram”, “density” “boxplot”, “violin”, or “densigram”\nBy default, marginal plots will appear for both axes. You can set margins = to “x” or “y” if you only want one\n\nOther optional arguments include fill = (bar color), color = (line color), size = (plot size relative to margin size, so larger number makes the marginal plot smaller)\nYou can provide other axis-specific arguments to xparams = and yparams =. For example, to have different histogram bin sizes, as shown below\n\nYou can have the marginal plots reflect groups (columns that have been assigned to color = in your ggplot() mapped aesthetics). If this is the case, set the ggMarginal() argument groupColour = or groupFill = to TRUE, as shown below.\nRead more at this vignette, in the R Graph Gallery or the function R documentation ?ggMarginal.\n\n# Install/load ggExtra\npacman::p_load(ggExtra)\n\n# Basic scatter plot of weight and age\nscatter_plot <- ggplot(data = linelist) +\n geom_point(mapping = aes(y = wt_kg, x = age)) +\n labs(title = \"Scatter plot of weight and age\")\n\nTo add marginal histograms use type = \"histogram\". You can optionally set groupFill = TRUE to get stacked histograms.\n\n# with histograms\nggMarginal(\n scatter_plot, # add marginal histograms\n type = \"histogram\", # specify histograms\n fill = \"lightblue\", # bar fill\n xparams = list(binwidth = 10), # other parameters for x-axis marginal\n yparams = list(binwidth = 5)) # other parameters for y-axis marginal\n\n\n\n\n\n\n\n\nMarginal density plot with grouped/colored values:\n\n# Scatter plot, colored by outcome\n# Outcome column is assigned as color in ggplot. groupFill in ggMarginal set to TRUE\nscatter_plot_color <- ggplot(data = linelist %>% drop_na(gender)) +\n geom_point(mapping = aes(y = wt_kg, x = age, color = gender)) +\n labs(title = \"Scatter plot of weight and age\") +\n theme(legend.position = \"bottom\")\n\nggMarginal(scatter_plot_color, type = \"density\", groupFill = TRUE)\n\n\n\n\n\n\n\n\nSet the size = arguemnt to adjust the relative size of the marginal plot. Smaller number makes a larger marginal plot. You also set color =. Below are is a marginal boxplot, with demonstration of the margins = argument so it appears on only one axis:\n\n# with boxplot \nggMarginal(\n scatter_plot,\n margins = \"x\", # only show x-axis marginal plot\n type = \"boxplot\")", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2870,7 +2870,7 @@ "href": "new_pages/ggplot_tips.html#smart-labeling", "title": "31  ggplot tips", "section": "31.6 Smart Labeling", - "text": "31.6 Smart Labeling\nIn ggplot2, it is also possible to add text to plots. However, this comes with the notable limitation where text labels often clash with data points in a plot, making them look messy or hard to read. There is no ideal way to deal with this in the base package, but there is a ggplot2 add-on, known as ggrepel that makes dealing with this very simple!\nThe ggrepel package provides two new functions, geom_label_repel() and geom_text_repel(), which replace geom_label() and geom_text(). Simply use these functions instead of the base functions to produce neat labels. Within the function, map the aesthetics aes() as always, but include the argument label = to which you provide a column name containing the values you want to display (e.g. patient id, or name, etc.). You can make more complex labels by combining columns and newlines (\\n) within str_glue() as shown below.\nA few tips:\n\nUse min.segment.length = 0 to always draw line segments, or min.segment.length = Inf to never draw them\n\nUse size = outside of aes() to set text size\n\nUse force = to change the degree of repulsion between labels and their respective points (default is 1)\n\nInclude fill = within aes() to have label colored by value\n\nA letter “a” may appear in the legend - add guides(fill = guide_legend(override.aes = aes(color = NA)))+ to remove it\n\n\nSee this is very in-depth tutorial for more.\n\npacman::p_load(ggrepel)\n\nlinelist %>% # start with linelist\n group_by(hospital) %>% # group by hospital\n summarise( # create new dataset with summary values per hospital\n n_cases = n(), # number of cases per hospital\n delay_mean = round(mean(days_onset_hosp, na.rm=T),1), # mean delay per hospital\n ) %>% \n ggplot(mapping = aes(x = n_cases, y = delay_mean))+ # send data frame to ggplot\n geom_point(size = 2)+ # add points\n geom_label_repel( # add point labels\n mapping = aes(\n label = stringr::str_glue(\n \"{hospital}\\n{n_cases} cases, {delay_mean} days\") # how label displays\n ), \n size = 3, # text size in labels\n min.segment.length = 0)+ # show all line segments \n labs( # add axes labels\n title = \"Mean delay to admission, by hospital\",\n x = \"Number of cases\",\n y = \"Mean delay (days)\")\n\n\n\n\n\n\n\n\nYou can label only a subset of the data points - by using standard ggplot() syntax to provide different data = for each geom layer of the plot. Below, All cases are plotted, but only a few are labeled.\n\nggplot()+\n # All points in grey\n geom_point(\n data = linelist, # all data provided to this layer\n mapping = aes(x = ht_cm, y = wt_kg),\n color = \"grey\",\n alpha = 0.5)+ # grey and semi-transparent\n \n # Few points in black\n geom_point(\n data = linelist %>% filter(days_onset_hosp > 15), # filtered data provided to this layer\n mapping = aes(x = ht_cm, y = wt_kg),\n alpha = 1)+ # default black and not transparent\n \n # point labels for few points\n geom_label_repel(\n data = linelist %>% filter(days_onset_hosp > 15), # filter the data for the labels\n mapping = aes(\n x = ht_cm,\n y = wt_kg,\n fill = outcome, # label color by outcome\n label = stringr::str_glue(\"Delay: {days_onset_hosp}d\")), # label created with str_glue()\n min.segment.length = 0) + # show line segments for all\n \n # remove letter \"a\" from inside legend boxes\n guides(fill = guide_legend(override.aes = aes(color = NA)))+\n \n # axis labels\n labs(\n title = \"Cases with long delay to admission\",\n y = \"weight (kg)\",\n x = \"height(cm)\")", + "text": "31.6 Smart Labeling\nIn ggplot2, it is also possible to add text to plots. However, this comes with the notable limitation where text labels often clash with data points in a plot, making them look messy or hard to read. There is no ideal way to deal with this in the base package, but there is a ggplot2 add-on, known as ggrepel that makes dealing with this very simple!\nThe ggrepel package provides two new functions, geom_label_repel() and geom_text_repel(), which replace geom_label() and geom_text(). Simply use these functions instead of the base functions to produce neat labels. Within the function, map the aesthetics aes() as always, but include the argument label = to which you provide a column name containing the values you want to display (e.g. patient id, or name, etc.). You can make more complex labels by combining columns and newlines (\\n) within str_glue() as shown below.\nA few tips:\n\nUse min.segment.length = 0 to always draw line segments, or min.segment.length = Inf to never draw them\nUse size = outside of aes() to set text size\nUse force = to change the degree of repulsion between labels and their respective points (default is 1)\n\nInclude fill = within aes() to have label colored by value\n\nA letter “a” may appear in the legend - add guides(fill = guide_legend(override.aes = aes(color = NA))) + to remove it\n\n\nSee this is very in-depth tutorial for more.\n\npacman::p_load(ggrepel)\n\nlinelist %>% # start with linelist\n group_by(hospital) %>% # group by hospital\n summarise( # create new dataset with summary values per hospital\n n_cases = n(), # number of cases per hospital\n delay_mean = round(mean(days_onset_hosp, na.rm=T),1), # mean delay per hospital\n ) %>% \n ggplot(mapping = aes(x = n_cases, y = delay_mean)) + # send data frame to ggplot\n geom_point(size = 2) + # add points\n geom_label_repel( # add point labels\n mapping = aes(\n label = stringr::str_glue(\n \"{hospital}\\n{n_cases} cases, {delay_mean} days\") # how label displays\n ), \n size = 3, # text size in labels\n min.segment.length = 0) + # show all line segments \n labs( # add axes labels\n title = \"Mean delay to admission, by hospital\",\n x = \"Number of cases\",\n y = \"Mean delay (days)\")\n\n\n\n\n\n\n\n\nYou can label only a subset of the data points - by using standard ggplot() syntax to provide different data = for each geom layer of the plot. Below, All cases are plotted, but only a few are labeled.\n\nggplot() +\n # All points in grey\n geom_point(\n data = linelist, # all data provided to this layer\n mapping = aes(x = ht_cm, y = wt_kg),\n color = \"grey\",\n alpha = 0.5) + # grey and semi-transparent\n \n # Few points in black\n geom_point(\n data = linelist %>% filter(days_onset_hosp > 15), # filtered data provided to this layer\n mapping = aes(x = ht_cm, y = wt_kg),\n alpha = 1) + # default black and not transparent\n \n # point labels for few points\n geom_label_repel(\n data = linelist %>% filter(days_onset_hosp > 15), # filter the data for the labels\n mapping = aes(\n x = ht_cm,\n y = wt_kg,\n fill = outcome, # label color by outcome\n label = stringr::str_glue(\"Delay: {days_onset_hosp}d\")), # label created with str_glue()\n min.segment.length = 0) + # show line segments for all\n \n # remove letter \"a\" from inside legend boxes\n guides(fill = guide_legend(override.aes = aes(color = NA))) +\n \n # axis labels\n labs(\n title = \"Cases with long delay to admission\",\n y = \"weight (kg)\",\n x = \"height(cm)\")", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2881,7 +2881,7 @@ "href": "new_pages/ggplot_tips.html#time-axes", "title": "31  ggplot tips", "section": "31.7 Time axes", - "text": "31.7 Time axes\nWorking with time axes in ggplot can seem daunting, but is made very easy with a few key functions. Remember that when working with time or date that you should ensure that the correct variables are formatted as date or datetime class - see the Working with dates page for more information on this, or [Epidemic curves] page (ggplot section) for examples.\nThe single most useful set of functions for working with dates in ggplot2 are the scale functions (scale_x_date(), scale_x_datetime(), and their cognate y-axis functions). These functions let you define how often you have axis labels, and how to format axis labels. To find out how to format dates, see the working with dates section again! You can use the date_breaks and date_labels arguments to specify how dates should look:\n\ndate_breaks allows you to specify how often axis breaks occur - you can pass a string here (e.g. \"3 months\", or “2 days\")\ndate_labels allows you to define the format dates are shown in. You can pass a date format string to these arguments (e.g. \"%b-%d-%Y\"):\n\n\n# make epi curve by date of onset when available\nggplot(linelist, aes(x = date_onset)) +\n geom_histogram(binwidth = 7) +\n scale_x_date(\n # 1 break every 1 month\n date_breaks = \"1 months\",\n # labels should show month then date\n date_labels = \"%b %d\"\n ) +\n theme_classic()\n\n\n\n\n\n\n\n\nOne easy solution to efficient date labels on the x-axis is to to assign the labels = argument in scale_x_date() to the function label_date_short() from the package scales. This function will automatically construct efficient date labels (read more here). An additional benefit of this function is that the labels will automatically adjust as your data expands over time, from days, to weeks, to months and years.\nSee a complete example in the Epicurves page section on multi-level date labels, but a quick example is shown below for reference:\n\nggplot(linelist, aes(x = date_onset)) +\n geom_histogram(binwidth = 7) +\n scale_x_date(\n labels = scales::label_date_short() # automatically efficient date labels\n )+\n theme_classic()", + "text": "31.7 Time axes\nWorking with time axes in ggplot can seem daunting, but is made very easy with a few key functions. Remember that when working with time or date that you should ensure that the correct variables are formatted as date or datetime class - see the Working with dates page for more information on this, or [Epidemic curves] page (ggplot section) for examples.\nThe single most useful set of functions for working with dates in ggplot2 are the scale functions (scale_x_date(), scale_x_datetime(), and their cognate y-axis functions). These functions let you define how often you have axis labels, and how to format axis labels. To find out how to format dates, see the working with dates section again! You can use the date_breaks and date_labels arguments to specify how dates should look:\n\ndate_breaks allows you to specify how often axis breaks occur - you can pass a string here (e.g. \"3 months\", or “2 days\")\ndate_labels allows you to define the format dates are shown in. You can pass a date format string to these arguments (e.g. \"%b-%d-%Y\")\n\n\n# make epi curve by date of onset when available\nggplot(linelist, \n mapping = aes(x = date_onset)) +\n geom_histogram(binwidth = 7) +\n scale_x_date(\n # 1 break every 1 month\n date_breaks = \"1 months\",\n # labels should show month then date\n date_labels = \"%b %d\"\n ) +\n theme_classic()\n\n\n\n\n\n\n\n\nOne easy solution to efficient date labels on the x-axis is to to assign the labels = argument in scale_x_date() to the function label_date_short() from the package scales. This function will automatically construct efficient date labels (read more here). An additional benefit of this function is that the labels will automatically adjust as your data expands over time, from days, to weeks, to months and years.\nSee a complete example in the Epicurves page section on multi-level date labels, but a quick example is shown below for reference:\n\nggplot(linelist, aes(x = date_onset)) +\n geom_histogram(binwidth = 7) +\n scale_x_date(\n labels = scales::label_date_short() # automatically efficient date labels\n ) +\n theme_classic()", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2892,7 +2892,7 @@ "href": "new_pages/ggplot_tips.html#highlighting", "title": "31  ggplot tips", "section": "31.8 Highlighting", - "text": "31.8 Highlighting\nHighlighting specific elements in a chart is a useful way to draw attention to a specific instance of a variable while also providing information on the dispersion of the full dataset. While this is not easily done in base ggplot2, there is an external package that can help to do this known as gghighlight. This is easy to use within the ggplot syntax.\nThe gghighlight package uses the gghighlight() function to achieve this effect. To use this function, supply a logical statement to the function - this can have quite flexible outcomes, but here we’ll show an example of the age distribution of cases in our linelist, highlighting them by outcome.\n\n# load gghighlight\nlibrary(gghighlight)\n\n# replace NA values with unknown in the outcome variable\nlinelist <- linelist %>%\n mutate(outcome = replace_na(outcome, \"Unknown\"))\n\n# produce a histogram of all cases by age\nggplot(\n data = linelist,\n mapping = aes(x = age_years, fill = outcome)) +\n geom_histogram() + \n gghighlight::gghighlight(outcome == \"Death\") # highlight instances where the patient has died.\n\n\n\n\n\n\n\n\nThis also works well with faceting functions - it allows the user to produce facet plots with the background data highlighted that doesn’t apply to the facet! Below we count cases by week and plot the epidemic curves by hospital (color = and facet_wrap() set to hospital column).\n\n# produce a histogram of all cases by age\nlinelist %>% \n count(week = lubridate::floor_date(date_hospitalisation, \"week\"),\n hospital) %>% \n ggplot()+\n geom_line(aes(x = week, y = n, color = hospital))+\n theme_minimal()+\n gghighlight::gghighlight() + # highlight instances where the patient has died\n facet_wrap(~hospital) # make facets by outcome", + "text": "31.8 Highlighting\nHighlighting specific elements in a chart is a useful way to draw attention to a specific instance of a variable while also providing information on the dispersion of the full dataset. While this is not easily done in base ggplot2, there is an external package that can help to do this known as gghighlight. This is easy to use within the ggplot syntax.\nThe gghighlight package uses the gghighlight() function to achieve this effect. To use this function, supply a logical statement to the function - this can have quite flexible outcomes, but here we’ll show an example of the age distribution of cases in our linelist, highlighting them by outcome.\n\n# load gghighlight\npacman::p_load(gghighlight)\n\n# replace NA values with unknown in the outcome variable\nlinelist <- linelist %>%\n mutate(outcome = replace_na(outcome, \"Unknown\"))\n\n# produce a histogram of all cases by age\nggplot(\n data = linelist,\n mapping = aes(x = age_years, fill = outcome)) +\n geom_histogram() + \n gghighlight::gghighlight(outcome == \"Death\") # highlight instances where the patient has died.\n\n\n\n\n\n\n\n\nThis also works well with faceting functions - it allows the user to produce facet plots with the background data highlighted that doesn’t apply to the facet! Below we count cases by week and plot the epidemic curves by hospital (color = and facet_wrap() set to hospital column).\n\n# produce a linegraph of all cases by age\nlinelist %>% \n count(week = lubridate::floor_date(date_hospitalisation, \"week\"),\n hospital) %>% \n ggplot() +\n geom_line(mapping = aes(x = week, \n y = n, \n color = hospital)) +\n theme_minimal() +\n gghighlight::gghighlight() + # highlight instances where the patient has died\n facet_wrap(~hospital) + # make facets by outcome\n scale_x_date(labels = date_format(\"%m/%y\"))", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2903,7 +2903,7 @@ "href": "new_pages/ggplot_tips.html#plotting-multiple-datasets", "title": "31  ggplot tips", "section": "31.9 Plotting multiple datasets", - "text": "31.9 Plotting multiple datasets\nNote that properly aligning axes to plot from multiple datasets in the same plot can be difficult. Consider one of the following strategies:\n\nMerge the data prior to plotting, and convert to “long” format with a column reflecting the dataset\n\nUse cowplot or a similar package to combine two plots (see below)", + "text": "31.9 Plotting multiple datasets\nNote that properly aligning axes to plot from multiple datasets in the same plot can be difficult. Consider one of the following strategies:\n\nMerge the data prior to plotting, and convert to “long” format with a column reflecting the dataset.\n\nUse patchwork or a similar package to combine two plots (see below).", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2914,7 +2914,7 @@ "href": "new_pages/ggplot_tips.html#combine-plots", "title": "31  ggplot tips", "section": "31.10 Combine plots", - "text": "31.10 Combine plots\nTwo packages that are very useful for combining plots are cowplot and patchwork. In this page we will mostly focus on cowplot, with occassional use of patchwork.\nHere is the online introduction to cowplot. You can read the more extensive documentation for each function online here. We will cover a few of the most common use cases and functions below.\nThe cowplot package works in tandem with ggplot2 - essentially, you use it to arrange and combine ggplots and their legends into compound figures. It can also accept base R graphics.\n\npacman::p_load(\n tidyverse, # data manipulation and visualisation\n cowplot, # combine plots\n patchwork # combine plots\n)\n\nWhile faceting (described in the ggplot basics page) is a convenient approach to plotting, sometimes its not possible to get the results you want from its relatively restrictive approach. Here, you may choose to combine plots by sticking them together into a larger plot. There are three well known packages that are great for this - cowplot, gridExtra, and patchwork. However, these packages largely do the same things, so we’ll focus on cowplot for this section.\n\nplot_grid()\nThe cowplot package has a fairly wide range of functions, but the easiest use of it can be achieved through the use of plot_grid(). This is effectively a way to arrange predefined plots in a grid formation. We can work through another example with the malaria dataset - here we can plot the total cases by district, and also show the epidemic curve over time.\n\nmalaria_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\")) \n\n# bar chart of total cases by district\np1 <- ggplot(malaria_data, aes(x = District, y = malaria_tot)) +\n geom_bar(stat = \"identity\") +\n labs(\n x = \"District\",\n y = \"Total number of cases\",\n title = \"Total malaria cases by district\"\n ) +\n theme_minimal()\n\n# epidemic curve over time\np2 <- ggplot(malaria_data, aes(x = data_date, y = malaria_tot)) +\n geom_col(width = 1) +\n labs(\n x = \"Date of data submission\",\n y = \"number of cases\"\n ) +\n theme_minimal()\n\ncowplot::plot_grid(p1, p2,\n # 1 column and two rows - stacked on top of each other\n ncol = 1,\n nrow = 2,\n # top plot is 2/3 as tall as second\n rel_heights = c(2, 3))\n\n\n\n\n\n\n\n\n\n\nCombine legends\nIf your plots have the same legend, combining them is relatively straight-forward. Simple use the cowplot approach above to combine the plots, but remove the legend from one of them (de-duplicate).\nIf your plots have different legends, you must use an alternative approach:\n\nCreate and save your plots without legends using theme(legend.position = \"none\")\n\nExtract the legends from each plot using get_legend() as shown below - but extract legends from the plots modified to actually show the legend\n\nCombine the legends into a legends panel\n\nCombine the plots and legends panel\n\nFor demonstration we show the two plots separately, and then arranged in a grid with their own legends showing (ugly and inefficient use of space):\n\np1 <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, outcome) %>% \n ggplot()+\n geom_col(mapping = aes(x = hospital, y = n, fill = outcome))+\n scale_fill_brewer(type = \"qual\", palette = 4, na.value = \"grey\")+\n coord_flip()+\n theme_minimal()+\n labs(title = \"Cases by outcome\")\n\n\np2 <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, age_cat) %>% \n ggplot()+\n geom_col(mapping = aes(x = hospital, y = n, fill = age_cat))+\n scale_fill_brewer(type = \"qual\", palette = 1, na.value = \"grey\")+\n coord_flip()+\n theme_minimal()+\n theme(axis.text.y = element_blank())+\n labs(title = \"Cases by age\")\n\nHere is how the two plots look when combined using plot_grid() without combining their legends:\n\ncowplot::plot_grid(p1, p2, rel_widths = c(0.3))\n\n\n\n\n\n\n\n\nAnd now we show how to combine the legends. Essentially what we do is to define each plot without its legend (theme(legend.position = \"none\"), and then we define each plot’s legend separately, using the get_legend() function from cowplot. When we extract the legend from the saved plot, we need to add + the legend back in, including specifying the placement (“right”) and smaller adjustments for alignment of the legends and their titles. Then, we combine the legends together vertically, and then combine the two plots with the newly-combined legends. Voila!\n\n# Define plot 1 without legend\np1 <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, outcome) %>% \n ggplot()+\n geom_col(mapping = aes(x = hospital, y = n, fill = outcome))+\n scale_fill_brewer(type = \"qual\", palette = 4, na.value = \"grey\")+\n coord_flip()+\n theme_minimal()+\n theme(legend.position = \"none\")+\n labs(title = \"Cases by outcome\")\n\n\n# Define plot 2 without legend\np2 <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, age_cat) %>% \n ggplot()+\n geom_col(mapping = aes(x = hospital, y = n, fill = age_cat))+\n scale_fill_brewer(type = \"qual\", palette = 1, na.value = \"grey\")+\n coord_flip()+\n theme_minimal()+\n theme(\n legend.position = \"none\",\n axis.text.y = element_blank(),\n axis.title.y = element_blank()\n )+\n labs(title = \"Cases by age\")\n\n\n# extract legend from p1 (from p1 + legend)\nleg_p1 <- cowplot::get_legend(p1 +\n theme(legend.position = \"right\", # extract vertical legend\n legend.justification = c(0,0.5))+ # so legends align\n labs(fill = \"Outcome\")) # title of legend\n# extract legend from p2 (from p2 + legend)\nleg_p2 <- cowplot::get_legend(p2 + \n theme(legend.position = \"right\", # extract vertical legend \n legend.justification = c(0,0.5))+ # so legends align\n labs(fill = \"Age Category\")) # title of legend\n\n# create a blank plot for legend alignment\n#blank_p <- patchwork::plot_spacer() + theme_void()\n\n# create legends panel, can be one on top of the other (or use spacer commented above)\nlegends <- cowplot::plot_grid(leg_p1, leg_p2, nrow = 2, rel_heights = c(.3, .7))\n\n# combine two plots and the combined legends panel\ncombined <- cowplot::plot_grid(p1, p2, legends, ncol = 3, rel_widths = c(.4, .4, .2))\n\ncombined # print\n\n\n\n\n\n\n\n\nThis solution was learned from this post with a minor fix to align legends from this post.\nTIP: Fun note - the “cow” in cowplot comes from the creator’s name - Claus O. Wilke.\n\n\nInset plots\nYou can inset one plot in another using cowplot. Here are things to be aware of:\n\nDefine the main plot with theme_half_open() from cowplot; it may be best to have the legend either on top or bottom\n\nDefine the inset plot. Best is to have a plot where you do not need a legend. You can remove plot theme elements with element_blank() as shown below.\n\nCombine them by applying ggdraw() to the main plot, then adding draw_plot() on the inset plot and specifying the coordinates (x and y of lower left corner), height and width as proportion of the whole main plot.\n\n\n# Define main plot\nmain_plot <- ggplot(data = linelist)+\n geom_histogram(aes(x = date_onset, fill = hospital))+\n scale_fill_brewer(type = \"qual\", palette = 1, na.value = \"grey\")+ \n theme_half_open()+\n theme(legend.position = \"bottom\")+\n labs(title = \"Epidemic curve and outcomes by hospital\")\n\n\n# Define inset plot\ninset_plot <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, outcome) %>% \n ggplot()+\n geom_col(mapping = aes(x = hospital, y = n, fill = outcome))+\n scale_fill_brewer(type = \"qual\", palette = 4, na.value = \"grey\")+\n coord_flip()+\n theme_minimal()+\n theme(legend.position = \"none\",\n axis.title.y = element_blank())+\n labs(title = \"Cases by outcome\") \n\n\n# Combine main with inset\ncowplot::ggdraw(main_plot)+\n draw_plot(inset_plot,\n x = .6, y = .55, #x = .07, y = .65,\n width = .4, height = .4)\n\n\n\n\n\n\n\n\nThis technique is explained more in these two vignettes:\nWilke lab\ndraw_plot() documentation", + "text": "31.10 Combine plots\nTwo packages that are very useful for combining plots are cowplot and patchwork. In this page we will mostly focus on patchwork.\nHere is the online introduction to cowplot. You can read the more extensive documentation for each function online here.\npatchwork allows us to combine separate ggplots into the same graphic using a very easy syntax to add, change layouts, and heavily customise our plots.\n\npacman::p_load(\n tidyverse, # data manipulation and visualisation\n patchwork # combine plots\n)\n\nWhile faceting (described in the ggplot basics page) is a convenient approach to plotting, sometimes its not possible to get the results you want from its relatively restrictive approach. Here, you may choose to combine plots by sticking them together into a larger plot. There are three well known packages that are great for this - cowplot, gridExtra, and patchwork. However, these packages largely do the same things, so we’ll focus on patchwork for this section.\n\nplot_grid()\nThe patchwork package has a very simple syntax for adding ggplot() objects together. You simply use +.\nThis is effectively a way to arrange predefined plots in a grid formation. We can work through another example with the malaria dataset - here we can plot the total cases by district, and also show the epidemic curve over time.\n\nmalaria_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\")) \n\n# bar chart of total cases by district\np1 <- ggplot(malaria_data, \n mapping = aes(x = District, \n y = malaria_tot)) +\n geom_bar(stat = \"identity\") +\n labs(\n x = \"District\",\n y = \"Total number of cases\",\n title = \"Total malaria cases by district\"\n ) +\n theme_minimal()\n\n# epidemic curve over time\np2 <- ggplot(malaria_data, \n mapping = aes(x = data_date, \n y = malaria_tot)) +\n geom_col(width = 1) +\n labs(\n x = \"Date of data submission\",\n y = \"number of cases\"\n ) +\n theme_minimal()\n\np1 + p2\n\n\n\n\n\n\n\n\nAnd if we wanted to put p1 above p2 we would use /\n\np1 / p2\n\n\n\n\n\n\n\n\nThere is also a large amount of flexibility in customising how the plots are arranged and sized. To do this we use the function plot_layout().\nThis allows us to customise things like the number of columns and rows we want our plots arranged on, and the widths and heights.\nFor instance if we wanted to put two plots on-top of each other, and make the bottom one smaller, we could this.\n\np1 + p2 + \n plot_layout(heights = c(3, 1))\n\nWarning: Removed 686 rows containing missing values or values outside the scale range\n(`geom_bar()`).\n\n\nWarning: Removed 686 rows containing missing values or values outside the scale range\n(`geom_col()`).\n\n\n\n\n\n\n\n\n\n\n\nCombine legends\nIf your plots have the same legend, with the same scale, combining them is relatively straight-forward. Simple use the patchwork approach above to combine the plots, but remove the legend from one of them (de-duplicate by setting theme(legend.position = \"none\")).\nIf you have two separate legends, by default the legend will be placed to the right of each plot.\n\np1 <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, outcome) %>% \n ggplot() +\n geom_col(mapping = aes(x = hospital, y = n, fill = outcome)) +\n scale_fill_brewer(type = \"qual\", palette = 4, na.value = \"grey\") +\n coord_flip() +\n theme_minimal() +\n labs(title = \"Cases by outcome\")\n\n\np2 <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, age_cat) %>% \n ggplot() +\n geom_col(mapping = aes(x = hospital, y = n, fill = age_cat)) +\n scale_fill_brewer(type = \"qual\", palette = 1, na.value = \"grey\") +\n coord_flip() +\n theme_minimal() +\n theme(axis.text.y = element_blank()) +\n labs(title = \"Cases by age\")\n\np1 + p2 \n\n\n\n\n\n\n\n\nThis can be an inefficient use of space. To place the two plots together on the same side, you can use the plot_layout() function. This function can control a number of different ways the plots are laid out, but here we will use it to place the legends on the same side.\n\np1 + p2 + plot_layout(guides = \"collect\")\n\n\n\n\n\n\n\n\nA much better use of space!\n\n\nInset plots\nYou can inset one plot in another using patchwork. This is done by using the function inset_element() which takes a few main arguments.\n\np = the plot you want to insert\nleft = the proportion along the plot you want the left side of the plot to be\nbottom = the proportion along the plot you want the bottom side of the plot to be\nright = the proportion along the plot you want the right side of the plot to be\ntop = the proportion along the plot you want the right side of the plot to be\n\n\n# Define main plot\nmain_plot <- ggplot(data = linelist) +\n geom_histogram(mapping = aes(x = date_onset, fill = hospital)) +\n scale_fill_brewer(type = \"qual\", palette = 1, na.value = \"grey\") + \n theme_minimal() +\n theme(legend.position = \"bottom\") +\n labs(title = \"Epidemic curve and outcomes by hospital\")\n\n\n# Define inset plot\ninset_plot <- linelist %>% \n mutate(hospital = recode(hospital, \"St. Mark's Maternity Hospital (SMMH)\" = \"St. Marks\")) %>% \n count(hospital, outcome) %>% \n ggplot() +\n geom_col(mapping = aes(x = hospital, y = n, fill = outcome)) +\n scale_fill_brewer(type = \"qual\", palette = 4, na.value = \"grey\") +\n coord_flip() +\n theme_minimal() +\n theme(legend.position = \"none\",\n axis.title.y = element_blank()) +\n labs(title = \"Cases by outcome\") \n\n\n# Combine main with inset\nmain_plot + inset_element(inset_plot, 0.6, 0.5, 1, 1)\n\n\n\n\n\n\n\n\nFurther details on insetting plots is found here.", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2958,7 +2958,7 @@ "href": "new_pages/ggplot_tips.html#resources", "title": "31  ggplot tips", "section": "31.14 Resources", - "text": "31.14 Resources\nInspiration ggplot graph gallery\nPresentation of data European Centre for Disease Prevention and Control Guidelines of presentation of surveillance data\nFacets and labellers Using labellers for facet strips Labellers\nAdjusting order with factors fct_reorder\nfct_inorder\nHow to reorder a boxplot\nReorder a variable in ggplot2\nR for Data Science - Factors\nLegends\nAdjust legend order\nCaptions Caption alignment\nLabels\nggrepel\nCheatsheets\nBeautiful plotting with ggplot2", + "text": "31.14 Resources\nInspiration ggplot graph gallery\nPresentation of data European Centre for Disease Prevention and Control Guidelines of presentation of surveillance data\nFacets and labellers Using labellers for facet strips Labellers\nAdjusting order with factors\nfct_reorder\nfct_inorder\nHow to reorder a boxplot\nReorder a variable in ggplot2\nR for Data Science - Factors\nLegends\nAdjust legend order\nCaptions Caption alignment\nLabels\nggrepel\nCheatsheets\nBeautiful plotting with ggplot2\nPlot alignment patchwork", "crumbs": [ "Data Visualization", "31  ggplot tips" @@ -2980,7 +2980,7 @@ "href": "new_pages/epicurves.html#preparation", "title": "32  Epidemic curves", "section": "", - "text": "Packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # file import/export\n here, # relative filepaths \n lubridate, # working with dates/epiweeks\n aweek, # alternative package for working with dates/epiweeks\n incidence2, # epicurves of linelist data\n i2extras, # supplement to incidence2\n stringr, # search and manipulate character strings\n forcats, # working with factors\n RColorBrewer, # Color palettes from colorbrewer2.org\n tidyverse # data management + ggplot2 graphics\n) \n\n\n\nImport data\nTwo example datasets are used in this section:\n\nLinelist of individual cases from a simulated epidemic\n\nAggregated counts by hospital from the same simulated epidemic\n\nThe datasets are imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\nCase linelist\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instruction in the Download handbook and data page. We assume the file is in the working directory so no sub-folders are specified in this file path.\n\nlinelist <- import(\"linelist_cleaned.xlsx\")\n\nThe first 50 rows are displayed below.\n\n\n\n\n\n\nCase counts aggregated by hospital\nFor the purposes of the handbook, the dataset of weekly aggregated counts by hospital is created from the linelist with the following code.\n\n# import the counts data into R\ncount_data <- linelist %>% \n group_by(hospital, date_hospitalisation) %>% \n summarize(n_cases = dplyr::n()) %>% \n filter(date_hospitalisation > as.Date(\"2013-06-01\")) %>% \n ungroup()\n\nThe first 50 rows are displayed below:\n\n\n\n\n\n\n\n\nSet parameters\nFor production of a report, you may want to set editable parameters such as the date for which the data is current (the “data date”). You can then reference the object data_date in your code when applying filters or in dynamic captions.\n\n## set the report date for the report\n## note: can be set to Sys.Date() for the current date\ndata_date <- as.Date(\"2015-05-15\")\n\n\n\nVerify dates\nVerify that each relevant date column is class Date and has an appropriate range of values. You can do this simply using hist() for histograms, or range() with na.rm=TRUE, or with ggplot() as below.\n\n# check range of onset dates\nggplot(data = linelist)+\n geom_histogram(aes(x = date_onset))", + "text": "Packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # file import/export\n tidyquant, # for moving averages\n here, # relative filepaths \n lubridate, # working with dates/epiweeks\n aweek, # alternative package for working with dates/epiweeks\n incidence2, # epicurves of linelist data\n i2extras, # supplement to incidence2\n stringr, # search and manipulate character strings\n forcats, # working with factors\n RColorBrewer, # Color palettes from colorbrewer2.org\n tidyverse # data management + ggplot2 graphics\n) \n\n\n\nImport data\nTwo example datasets are used in this section:\n\nLinelist of individual cases from a simulated epidemic.\n\nAggregated counts by hospital from the same simulated epidemic.\n\nThe datasets are imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\n\nWarning: Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\n\n\nCase linelist\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instruction in the Download handbook and data page. We assume the file is in the working directory so no sub-folders are specified in this file path.\n\nlinelist <- import(\"linelist_cleaned.xlsx\")\n\nThe first 50 rows are displayed below.\n\n\n\n\n\n\nCase counts aggregated by hospital\nFor the purposes of the handbook, the dataset of weekly aggregated counts by hospital is created from the linelist with the following code.\n\n# import the counts data into R\ncount_data <- linelist %>% \n group_by(hospital, date_hospitalisation) %>% \n summarize(n_cases = dplyr::n()) %>% \n filter(date_hospitalisation > as.Date(\"2013-06-01\")) %>% \n ungroup()\n\nThe first 50 rows are displayed below:\n\n\n\n\n\n\n\n\nSet parameters\nFor production of a report, you may want to set editable parameters such as the date for which the data is current (the “data date”). You can then reference the object data_date in your code when applying filters or in dynamic captions.\n\n## set the report date for the report\n## note: can be set to Sys.Date() for the current date\ndata_date <- as.Date(\"2015-05-15\")\n\n\n\nVerify dates\nVerify that each relevant date column is class Date and has an appropriate range of values. You can do this simply using hist() for histograms, or range() with na.rm=TRUE, or with ggplot() as below.\n\n# check range of onset dates\nggplot(data = linelist) +\n geom_histogram(aes(x = date_onset))", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -2991,7 +2991,7 @@ "href": "new_pages/epicurves.html#epicurves-with-ggplot2", "title": "32  Epidemic curves", "section": "32.2 Epicurves with ggplot2", - "text": "32.2 Epicurves with ggplot2\nUsing ggplot() to build your epicurve allows for flexibility and customization, but requires more effort and understanding of how ggplot() works.\nYou must manually control the aggregation of the cases by time (into weeks, months, etc) and the intervals of the labels on the date axis. This must be carefully managed.\nThese examples use a subset of the linelist dataset - only the cases from Central Hospital.\n\ncentral_data <- linelist %>% \n filter(hospital == \"Central Hospital\")\n\nTo produce an epicurve with ggplot() there are three main elements:\n\nA histogram, with linelist cases aggregated into “bins” distinguished by specific “break” points\n\nScales for the axes and their labels\n\nThemes for the plot appearance, including titles, labels, captions, etc.\n\n\nSpecify case bins\nHere we show how to specify how cases will be aggregated into histogram bins (“bars”). It is important to recognize that the aggregation of cases into histogram bins is not necessarily the same intervals as the dates that will appear on the x-axis.\nBelow is perhaps the most simple code to produce daily and weekly epicurves.\nIn the over-arching ggplot() command the dataset is provided to data =. Onto this foundation, the geometry of a histogram is added with a +. Within the geom_histogram(), we map the aesthetics such that the column date_onset is mapped to the x-axis. Also within the geom_histogram() but not within aes() we set the binwidth = of the histogram bins, in days. If this ggplot2 syntax is confusing, review the page on ggplot basics.\nCAUTION: Plotting weekly cases by using binwidth = 7 starts the first 7-day bin at the first case, which could be any day of the week! To create specific weeks, see section below .\n\n# daily \nggplot(data = central_data) + # set data\n geom_histogram( # add histogram\n mapping = aes(x = date_onset), # map date column to x-axis\n binwidth = 1)+ # cases binned by 1 day \n labs(title = \"Central Hospital - Daily\") # title\n\n# weekly\nggplot(data = central_data) + # set data \n geom_histogram( # add histogram\n mapping = aes(x = date_onset), # map date column to x-axis\n binwidth = 7)+ # cases binned every 7 days, starting from first case (!) \n labs(title = \"Central Hospital - 7-day bins, starting at first case\") # title\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLet us note that the first case in this Central Hospital dataset had symptom onset on:\n\nformat(min(central_data$date_onset, na.rm=T), \"%A %d %b, %Y\")\n\n[1] \"Thursday 01 May, 2014\"\n\n\nTo manually specify the histogram bin breaks, do not use the binwidth = argument, and instead supply a vector of dates to breaks =.\nCreate the vector of dates with the base R function seq.Date(). This function expects arguments to =, from =, and by =. For example, the command below returns monthly dates starting at Jan 15 and ending by June 28.\n\nmonthly_breaks <- seq.Date(from = as.Date(\"2014-02-01\"),\n to = as.Date(\"2015-07-15\"),\n by = \"months\")\n\nmonthly_breaks # print\n\n [1] \"2014-02-01\" \"2014-03-01\" \"2014-04-01\" \"2014-05-01\" \"2014-06-01\"\n [6] \"2014-07-01\" \"2014-08-01\" \"2014-09-01\" \"2014-10-01\" \"2014-11-01\"\n[11] \"2014-12-01\" \"2015-01-01\" \"2015-02-01\" \"2015-03-01\" \"2015-04-01\"\n[16] \"2015-05-01\" \"2015-06-01\" \"2015-07-01\"\n\n\nThis vector can be provided to geom_histogram() as breaks =:\n\n# monthly \nggplot(data = central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n breaks = monthly_breaks)+ # provide the pre-defined vector of breaks \n labs(title = \"Monthly case bins\") # title\n\n\n\n\n\n\n\n\nA simple weekly date sequence can be returned by setting by = \"week\". For example:\n\nweekly_breaks <- seq.Date(from = as.Date(\"2014-02-01\"),\n to = as.Date(\"2015-07-15\"),\n by = \"week\")\n\nAn alternative to supplying specific start and end dates is to write dynamic code so that weekly bins begin the Monday before the first case. We will use these date vectors throughout the examples below.\n\n# Sequence of weekly Monday dates for CENTRAL HOSPITAL\nweekly_breaks_central <- seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 1), # monday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 1), # monday after last case\n by = \"week\")\n\nLet’s unpack the rather daunting code above:\n\nThe “from” value (earliest date of the sequence) is created as follows: the minimum date value (min() with na.rm=TRUE) in the column date_onset is fed to floor_date() from the lubridate package. floor_date() set to “week” returns the start date of that cases’s “week”, given that the start day of each week is a Monday (week_start = 1).\n\nLikewise, the “to” value (end date of the sequence) is created using the inverse function ceiling_date() to return the Monday after the last case.\n\nThe “by” argument of seq.Date() can be set to any number of days, weeks, or months.\n\nUse week_start = 7 for Sunday weeks\n\nAs we will use these date vectors throughout this page, we also define one for the whole outbreak (the above is for Central Hospital only).\n\n# Sequence for the entire outbreak\nweekly_breaks_all <- seq.Date(\n from = floor_date(min(linelist$date_onset, na.rm=T), \"week\", week_start = 1), # monday before first case\n to = ceiling_date(max(linelist$date_onset, na.rm=T), \"week\", week_start = 1), # monday after last case\n by = \"week\")\n\nThese seq.Date() outputs can be used to create histogram bin breaks, but also the breaks for the date labels, which may be independent from the bins. Read more about the date labels in later sections.\nTIP: For a more simple ggplot() command, save the bin breaks and date label breaks as named vectors in advance, and simply provide their names to breaks =.\n\n\nWeekly epicurve example\nBelow is detailed example code to produce weekly epicurves for Monday weeks, with aligned bars, date labels, and vertical gridlines. This section is for the user who needs code quickly. To understand each aspect (themes, date labels, etc.) in-depth, continue to the subsequent sections. Of note:\n\nThe histogram bin breaks are defined with seq.Date() as explained above to begin the Monday before the earliest case and to end the Monday after the last case\n\nThe interval of date labels is specified by date_breaks = within scale_x_date()\n\nThe interval of minor vertical gridlines between date labels is specified to date_minor_breaks =\n\nWe use closed = \"left\" in the geom_histogram() to ensure the date are counted in the correct bins\n\nexpand = c(0,0) in the x and y scales removes excess space on each side of the axes, which also ensures the date labels begin from the first bar.\n\n\n# TOTAL MONDAY WEEK ALIGNMENT\n#############################\n# Define sequence of weekly breaks\nweekly_breaks_central <- seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 1), # Monday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 1), # Monday after last case\n by = \"week\") # bins are 7-days \n\n\nggplot(data = central_data) + \n \n # make histogram: specify bin break points: starts the Monday before first case, end Monday after last case\n geom_histogram(\n \n # mapping aesthetics\n mapping = aes(x = date_onset), # date column mapped to x-axis\n \n # histogram bin breaks\n breaks = weekly_breaks_central, # histogram bin breaks defined previously\n \n closed = \"left\", # count cases from start of breakpoint\n \n # bars\n color = \"darkblue\", # color of lines around bars\n fill = \"lightblue\" # color of fill within bars\n )+ \n \n # x-axis labels\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space before and after case bars\n date_breaks = \"4 weeks\", # date labels and major vertical gridlines appear every 3 Monday weeks\n date_minor_breaks = \"week\", # minor vertical lines appear every Monday week\n date_labels = \"%a\\n%d %b\\n%Y\")+ # date labels format\n \n # y-axis\n scale_y_continuous(\n expand = c(0,0))+ # remove excess y-axis space below 0 (align histogram flush with x-axis)\n \n # aesthetic themes\n theme_minimal()+ # simplify plot background\n \n theme(\n plot.caption = element_text(hjust = 0, # caption on left side\n face = \"italic\"), # caption in italics\n axis.title = element_text(face = \"bold\"))+ # axis titles in bold\n \n # labels including dynamic caption\n labs(\n title = \"Weekly incidence of cases (Monday weeks)\",\n subtitle = \"Note alignment of bars, vertical gridlines, and axis labels on Monday weeks\",\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))\n\n\n\n\n\n\n\n\n\nSunday weeks\nTo achieve the above plot for Sunday weeks a few modifications are needed, because the date_breaks = \"weeks\" only work for Monday weeks.\n\nThe break points of the histogram bins must be set to Sundays (week_start = 7)\n\nWithin scale_x_date(), the similar date breaks should be provided to breaks = and minor_breaks = to ensure the date labels and vertical gridlines align on Sundays.\n\nFor example, the scale_x_date() command for Sunday weeks could look like this:\n\nscale_x_date(\n expand = c(0,0),\n \n # specify interval of date labels and major vertical gridlines\n breaks = seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7), # Sunday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7), # Sunday after last case\n by = \"4 weeks\"),\n \n # specify interval of minor vertical gridline \n minor_breaks = seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7), # Sunday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7), # Sunday after last case\n by = \"week\"),\n \n # date label format\n #date_labels = \"%a\\n%d %b\\n%Y\")+ # day, above month abbrev., above 2-digit year\n label = scales::label_date_short())+ # automatic label formatting\n\n\n\n\nGroup/color by value\nThe histogram bars can be colored by group and “stacked”. To designate the grouping column, make the following changes. See the ggplot basics page for details.\n\nWithin the histogram aesthetic mapping aes(), map the column name to the group = and fill = arguments\n\nRemove any fill = argument outside of aes(), as it will override the one inside\n\nArguments inside aes() will apply by group, whereas any outside will apply to all bars (e.g. you may still want color = outside, so each bar has the same border)\n\nHere is what the aes() command would look like to group and color the bars by gender:\n\naes(x = date_onset, group = gender, fill = gender)\n\nHere it is applied:\n\nggplot(data = linelist) + # begin with linelist (many hospitals)\n \n # make histogram: specify bin break points: starts the Monday before first case, end Monday after last case\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = hospital, # set data to be grouped by hospital\n fill = hospital), # bar fill (inside color) by hospital\n \n # bin breaks are Monday weeks\n breaks = weekly_breaks_all, # sequence of weekly Monday bin breaks for whole outbreak, defined in previous code \n \n closed = \"left\", # count cases from start of breakpoint\n\n # Color around bars\n color = \"black\")\n\n\n\n\n\n\n\n\n\n\nAdjust colors\n\nTo manually set the fill for each group, use scale_fill_manual() (note: scale_color_manual() is different!).\n\nUse the values = argument to apply a vector of colors.\n\nUse na.value = to specify a color for NA values.\n\nUse the labels = argument to change the text of legend items. To be safe, provide as a named vector like c(\"old\" = \"new\", \"old\" = \"new\") or adjust the values in the data itself.\n\nUse name = to give a proper title to the legend\n\n\nFor more tips on color scales and palettes, see the page on ggplot basics.\n\n\nggplot(data = linelist)+ # begin with linelist (many hospitals)\n \n # make histogram\n geom_histogram(\n mapping = aes(x = date_onset,\n group = hospital, # cases grouped by hospital\n fill = hospital), # bar fill by hospital\n \n # bin breaks\n breaks = weekly_breaks_all, # sequence of weekly Monday bin breaks, defined in previous code\n \n closed = \"left\", # count cases from start of breakpoint\n\n # Color around bars\n color = \"black\")+ # border color of each bar\n \n # manual specification of colors\n scale_fill_manual(\n values = c(\"black\", \"orange\", \"grey\", \"beige\", \"blue\", \"brown\"),\n labels = c(\"St. Mark's Maternity Hospital (SMMH)\" = \"St. Mark's\"),\n name = \"Hospital\") # specify fill colors (\"values\") - attention to order!\n\n\n\n\n\n\n\n\n\n\nAdjust level order\nThe order in which grouped bars are stacked is best adjusted by classifying the grouping column as class Factor. You can then designate the factor level order (and their display labels). See the page on Factors or ggplot tips for details.\nBefore making the plot, use the fct_relevel() function from forcats package to convert the grouping column to class factor and manually adjust the level order, as detailed in the page on Factors.\n\n# load forcats package for working with factors\npacman::p_load(forcats)\n\n# Define new dataset with hospital as factor\nplot_data <- linelist %>% \n mutate(hospital = fct_relevel(hospital, c(\"Missing\", \"Other\"))) # Convert to factor and set \"Missing\" and \"Other\" as top levels to appear on epicurve top\n\nlevels(plot_data$hospital) # print levels in order\n\n[1] \"Missing\" \n[2] \"Other\" \n[3] \"Central Hospital\" \n[4] \"Military Hospital\" \n[5] \"Port Hospital\" \n[6] \"St. Mark's Maternity Hospital (SMMH)\"\n\n\nIn the below plot, the only differences from previous is that column hospital has been consolidated as above, and we use guides() to reverse the legend order, so that “Missing” is on the bottom of the legend.\n\nggplot(plot_data) + # Use NEW dataset with hospital as re-ordered factor\n \n # make histogram\n geom_histogram(\n mapping = aes(x = date_onset,\n group = hospital, # cases grouped by hospital\n fill = hospital), # bar fill (color) by hospital\n \n breaks = weekly_breaks_all, # sequence of weekly Monday bin breaks for whole outbreak, defined at top of ggplot section\n \n closed = \"left\", # count cases from start of breakpoint\n\n color = \"black\")+ # border color around each bar\n \n # x-axis labels\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space before and after case bars\n date_breaks = \"3 weeks\", # labels appear every 3 Monday weeks\n date_minor_breaks = \"week\", # vertical lines appear every Monday week\n label = scales::label_date_short()) + # efficient label formatting\n \n # y-axis\n scale_y_continuous(\n expand = c(0,0))+ # remove excess y-axis space below 0\n \n # manual specification of colors, ! attention to order\n scale_fill_manual(\n values = c(\"grey\", \"beige\", \"black\", \"orange\", \"blue\", \"brown\"),\n labels = c(\"St. Mark's Maternity Hospital (SMMH)\" = \"St. Mark's\"),\n name = \"Hospital\")+ \n \n # aesthetic themes\n theme_minimal()+ # simplify plot background\n \n theme(\n plot.caption = element_text(face = \"italic\", # caption on left side in italics\n hjust = 0), \n axis.title = element_text(face = \"bold\"))+ # axis titles in bold\n \n # labels\n labs(\n title = \"Weekly incidence of cases by hospital\",\n subtitle = \"Hospital as re-ordered factor\",\n x = \"Week of symptom onset\",\n y = \"Weekly cases\")\n\n\n\n\n\n\n\n\nTIP: To reverse the order of the legend only, add this ggplot2 command: guides(fill = guide_legend(reverse = TRUE)).\n\n\nAdjust legend\nRead more about legends and scales in the ggplot tips page. Here are a few highlights:\n\nEdit legend title either in the scale function or with labs(fill = \"Legend title\") (if your are using color = aesthetic, then use labs(color = \"\"))\n\ntheme(legend.title = element_blank()) to have no legend title\n\ntheme(legend.position = \"top\") (“bottom”, “left”, “right”, or “none” to remove the legend)\ntheme(legend.direction = \"horizontal\") horizontal legend\nguides(fill = guide_legend(reverse = TRUE)) to reverse order of the legend\n\n\n\nBars side-by-side\nSide-by-side display of group bars (as opposed to stacked) is specified within geom_histogram() with position = \"dodge\" placed outside of aes().\nIf there are more than two value groups, these can become difficult to read. Consider instead using a faceted plot (small multiples). To improve readability in this example, missing gender values are removed.\n\nggplot(central_data %>% drop_na(gender))+ # begin with Central Hospital cases dropping missing gender\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = gender, # cases grouped by gender\n fill = gender), # bars filled by gender\n \n # histogram bin breaks\n breaks = weekly_breaks_central, # sequence of weekly dates for Central outbreak - defined at top of ggplot section\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"black\", # bar edge color\n \n position = \"dodge\")+ # SIDE-BY-SIDE bars\n \n \n # The labels on the x-axis\n scale_x_date(expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"3 weeks\", # labels appear every 3 Monday weeks\n date_minor_breaks = \"week\", # vertical lines appear every Monday week\n label = scales::label_date_short())+ # efficient date labels\n \n # y-axis\n scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space between bottom of bars and the labels\n \n #scale of colors and legend labels\n scale_fill_manual(values = c(\"brown\", \"orange\"), # specify fill colors (\"values\") - attention to order!\n na.value = \"grey\" )+ \n\n # aesthetic themes\n theme_minimal()+ # a set of themes to simplify plot\n theme(plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"))+ # axis titles in bold\n \n # labels\n labs(title = \"Weekly incidence of cases, by gender\",\n subtitle = \"Subtitle\",\n fill = \"Gender\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\")\n\n\n\n\n\n\n\n\n\n\nAxis limits\nThere are two ways to limit the extent of axis values.\nGenerally the preferred way is to use the command coord_cartesian(), which accepts xlim = c(min, max) and ylim = c(min, max) (where you provide the min and max values). This acts as a “zoom” without actually removing any data, which is important for statistics and summary measures.\nAlternatively, you can set maximum and minimum date values using limits = c() within scale_x_date(). For example:\n\nscale_x_date(limits = c(as.Date(\"2014-04-01\"), NA)) # sets a minimum date but leaves the maximum open. \n\nLikewise, if you want to the x-axis to extend to a specific date (e.g. current date), even if no new cases have been reported, you can use:\n\nscale_x_date(limits = c(NA, Sys.Date()) # ensures date axis will extend until current date \n\nDANGER: Be cautious setting the y-axis scale breaks or limits (e.g. 0 to 30 by 5: seq(0, 30, 5)). Such static numbers can cut-off your plot too short if the data changes to exceed the limit!.\n\n\nDate-axis labels/gridlines\nTIP: Remember that date-axis labels are independent from the aggregation of the data into bars, but visually it can be important to align bins, date labels, and vertical grid lines.\nTo modify the date labels and grid lines, use scale_x_date() in one of these ways:\n\nIf your histogram bins are days, Monday weeks, months, or years:\n\nUse date_breaks = to specify the interval of labels and major gridlines (e.g. “day”, “week”, “3 weeks”, “month”, or “year”)\nUse date_minor_breaks = to specify interval of minor vertical gridlines (between date labels)\n\nAdd expand = c(0,0) to begin the labels at the first bar\n\nUse date_labels = to specify format of date labels - see the Dates page for tips (use \\n for a new line)\n\n\nIf your histogram bins are Sunday weeks:\n\nUse breaks = and minor_breaks = by providing a sequence of date breaks for each\nYou can still use date_labels = and expand = for formatting as described above\n\n\nSome notes:\n\nSee the opening ggplot section for instructions on how to create a sequence of dates using seq.Date().\n\nSee this page or the Working with dates page for tips on creating date labels.\n\n\nDemonstrations\nBelow is a demonstration of plots where the bins and the plot labels/grid lines are aligned and not aligned:\n\n# 7-day bins + Monday labels\n#############################\nggplot(central_data) +\n geom_histogram(\n mapping = aes(x = date_onset),\n binwidth = 7, # 7-day bins with start at first case\n color = \"darkblue\",\n fill = \"lightblue\") +\n \n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"3 weeks\", # Monday every 3 weeks\n date_minor_breaks = \"week\", # Monday weeks\n label = scales::label_date_short())+ # automatic label formatting\n \n scale_y_continuous(\n expand = c(0,0))+ # remove excess space under x-axis, make flush\n \n labs(\n title = \"MISALIGNED\",\n subtitle = \"! CAUTION: 7-day bars start Thursdays at first case\\nDate labels and gridlines on Mondays\\nNote how ticks don't align with bars\")\n\n\n\n# 7-day bins + Months\n#####################\nggplot(central_data) +\n geom_histogram(\n mapping = aes(x = date_onset),\n binwidth = 7,\n color = \"darkblue\",\n fill = \"lightblue\") +\n \n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"months\", # 1st of month\n date_minor_breaks = \"week\", # Monday weeks\n label = scales::label_date_short())+ # automatic label formatting\n \n scale_y_continuous(\n expand = c(0,0))+ # remove excess space under x-axis, make flush \n \n labs(\n title = \"MISALIGNED\",\n subtitle = \"! CAUTION: 7-day bars start Thursdays with first case\\nMajor gridlines and date labels at 1st of each month\\nMinor gridlines weekly on Mondays\\nNote uneven spacing of some gridlines and ticks unaligned with bars\")\n\n\n# TOTAL MONDAY ALIGNMENT: specify manual bin breaks to be mondays\n#################################################################\nggplot(central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n \n # histogram breaks set to 7 days beginning Monday before first case\n breaks = weekly_breaks_central, # defined earlier in this page\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"darkblue\",\n \n fill = \"lightblue\") + \n \n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"4 weeks\", # Monday every 4 weeks\n date_minor_breaks = \"week\", # Monday weeks \n label = scales::label_date_short())+ # label formatting\n \n scale_y_continuous(\n expand = c(0,0))+ # remove excess space under x-axis, make flush \n \n labs(\n title = \"ALIGNED Mondays\",\n subtitle = \"7-day bins manually set to begin Monday before first case (28 Apr)\\nDate labels and gridlines on Mondays as well\")\n\n\n# TOTAL MONDAY ALIGNMENT WITH MONTHS LABELS:\n############################################\nggplot(central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n \n # histogram breaks set to 7 days beginning Monday before first case\n breaks = weekly_breaks_central, # defined earlier in this page\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"darkblue\",\n \n fill = \"lightblue\") + \n \n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"months\", # Monday every 4 weeks\n date_minor_breaks = \"week\", # Monday weeks \n label = scales::label_date_short())+ # label formatting\n \n scale_y_continuous(\n expand = c(0,0))+ # remove excess space under x-axis, make flush \n \n theme(panel.grid.major = element_blank())+ # Remove major gridlines (fall on 1st of month)\n \n labs(\n title = \"ALIGNED Mondays with MONTHLY labels\",\n subtitle = \"7-day bins manually set to begin Monday before first case (28 Apr)\\nDate labels on 1st of Month\\nMonthly major gridlines removed\")\n\n\n# TOTAL SUNDAY ALIGNMENT: specify manual bin breaks AND labels to be Sundays\n############################################################################\nggplot(central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n \n # histogram breaks set to 7 days beginning Sunday before first case\n breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n by = \"7 days\"),\n \n closed = \"left\", # count cases from start of breakpoint\n\n color = \"darkblue\",\n \n fill = \"lightblue\") + \n \n scale_x_date(\n expand = c(0,0),\n # date label breaks and major gridlines set to every 3 weeks beginning Sunday before first case\n breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n by = \"3 weeks\"),\n \n # minor gridlines set to weekly beginning Sunday before first case\n minor_breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n by = \"7 days\"),\n \n label = scales::label_date_short())+ # label formatting\n \n scale_y_continuous(\n expand = c(0,0))+ # remove excess space under x-axis, make flush \n \n labs(title = \"ALIGNED Sundays\",\n subtitle = \"7-day bins manually set to begin Sunday before first case (27 Apr)\\nDate labels and gridlines manually set to Sundays as well\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAggregated data\nOften instead of a linelist, you begin with aggregated counts from facilities, districts, etc. You can make an epicurve with ggplot() but the code will be slightly different. This section will utilize the count_data dataset that was imported earlier, in the data preparation section. This dataset is the linelist aggregated to day-hospital counts. The first 50 rows are displayed below.\n\n\n\n\n\n\n\nPlotting daily counts\nWe can plot a daily epicurve from these daily counts. Here are the differences to the code:\n\nWithin the aesthetic mapping aes(), specify y = as the counts column (in this case, the column name is n_cases)\nAdd the argument stat = \"identity\" within geom_histogram(), which specifies that bar height should be the y = value, not the number of rows as is the default\n\nAdd the argument width = to avoid vertical white lines between the bars. For daily data set to 1. For weekly count data set to 7. For monthly count data, white lines are an issue (each month has different number of days) - consider transforming your x-axis to a categorical ordered factor (months) and using geom_col().\n\n\nggplot(data = count_data)+\n geom_histogram(\n mapping = aes(x = date_hospitalisation, y = n_cases),\n stat = \"identity\",\n width = 1)+ # for daily counts, set width = 1 to avoid white space between bars\n labs(\n x = \"Date of report\", \n y = \"Number of cases\",\n title = \"Daily case incidence, from daily count data\")\n\n\n\n\n\n\n\n\n\n\nPlotting weekly counts\nIf your data are already case counts by week, they might look like this dataset (called count_data_weekly):\nThe first 50 rows of count_data_weekly are displayed below. You can see that the counts have been aggregated into weeks. Each week is displayed by the first day of the week (Monday by default).\n\n\n\n\n\n\nNow plot so that x = the epiweek column. Remember to add y = the counts column to the aesthetic mapping, and add stat = \"identity\" as explained above.\n\nggplot(data = count_data_weekly)+\n \n geom_histogram(\n mapping = aes(\n x = epiweek, # x-axis is epiweek (as class Date)\n y = n_cases_weekly, # y-axis height in the weekly case counts\n group = hospital, # we are grouping the bars and coloring by hospital\n fill = hospital),\n stat = \"identity\")+ # this is also required when plotting count data\n \n # labels for x-axis\n scale_x_date(\n date_breaks = \"2 months\", # labels every 2 months \n date_minor_breaks = \"1 month\", # gridlines every month\n label = scales::label_date_short())+ # label formatting\n \n # Choose color palette (uses RColorBrewer package)\n scale_fill_brewer(palette = \"Pastel2\")+ \n \n theme_minimal()+\n \n labs(\n x = \"Week of onset\", \n y = \"Weekly case incidence\",\n fill = \"Hospital\",\n title = \"Weekly case incidence, from aggregated count data by hospital\")\n\n\n\n\n\n\n\n\n\n\n\nMoving averages\nSee the page on Moving averages for a detailed description and several options. Below is one option for calculating moving averages with the package slider. In this approach, the moving average is calculated in the dataset prior to plotting:\n\nAggregate the data into counts as necessary (daily, weekly, etc.) (see Grouping data page)\n\nCreate a new column to hold the moving average, created with slide_index() from slider package\n\nPlot the moving average as a geom_line() on top of (after) the epicurve histogram\n\nSee the helpful online vignette for the slider package\n\n# load package\npacman::p_load(slider) # slider used to calculate rolling averages\n\n# make dataset of daily counts and 7-day moving average\n#######################################################\nll_counts_7day <- linelist %>% # begin with linelist\n \n ## count cases by date\n count(date_onset, name = \"new_cases\") %>% # name new column with counts as \"new_cases\"\n drop_na(date_onset) %>% # remove cases with missing date_onset\n \n ## calculate the average number of cases in 7-day window\n mutate(\n avg_7day = slider::slide_index( # create new column\n new_cases, # calculate based on value in new_cases column\n .i = date_onset, # index is date_onset col, so non-present dates are included in window \n .f = ~mean(.x, na.rm = TRUE), # function is mean() with missing values removed\n .before = 6, # window is the day and 6-days before\n .complete = FALSE), # must be FALSE for unlist() to work in next step\n avg_7day = unlist(avg_7day)) # convert class list to class numeric\n\n\n# plot\n######\nggplot(data = ll_counts_7day) + # begin with new dataset defined above \n geom_histogram( # create epicurve histogram\n mapping = aes(\n x = date_onset, # date column as x-axis\n y = new_cases), # height is number of daily new cases\n stat = \"identity\", # height is y value\n fill=\"#92a8d1\", # cool color for bars\n colour = \"#92a8d1\", # same color for bar border\n )+ \n geom_line( # make line for rolling average\n mapping = aes(\n x = date_onset, # date column for x-axis\n y = avg_7day, # y-value set to rolling average column\n lty = \"7-day \\nrolling avg\"), # name of line in legend\n color=\"red\", # color of line\n size = 1) + # width of line\n scale_x_date( # date scale\n date_breaks = \"1 month\",\n label = scales::label_date_short(), # label formatting\n expand = c(0,0)) +\n scale_y_continuous( # y-axis scale\n expand = c(0,0),\n limits = c(0, NA)) + \n labs(\n x=\"\",\n y =\"Number of confirmed cases\",\n fill = \"Legend\")+ \n theme_minimal()+\n theme(legend.title = element_blank()) # removes title of legend\n\n\n\n\n\n\n\n\n\n\nFaceting/small-multiples\nAs with other ggplots, you can create facetted plots (“small multiples”). As explained in the ggplot tips page of this handbook, you can use either facet_wrap() or facet_grid(). Here we demonstrate with facet_wrap(). For epicurves, facet_wrap() is typically easier as it is likely that you only need to facet on one column.\nThe general syntax is facet_wrap(rows ~ cols), where to the left of the tilde (~) is the name of a column to be spread across the “rows” of the facetted plot, and to the right of the tilde is the name of a column to be spread across the “columns” of the facetted plot. Most simply, just use one column name, to the right of the tilde: facet_wrap(~age_cat).\nFree axes\nYou will need to decide whether the scales of the axes for each facet are “fixed” to the same dimensions (default), or “free” (meaning they will change based on the data within the facet). Do this with the scales = argument within facet_wrap() by specifying “free_x” or “free_y”, or “free”.\nNumber of cols and rows of facets\nThis can be specified with ncol = and nrow = within facet_wrap().\nOrder of panels\nTo change the order of appearance, change the underlying order of the levels of the factor column used to create the facets.\nAesthetics\nFont size and face, strip color, etc. can be modified through theme() with arguments like:\n\nstrip.text = element_text() (size, colour, face, angle…)\nstrip.background = element_rect() (e.g. element_rect(fill=“grey”))\n\nstrip.position = (position of the strip “bottom”, “top”, “left”, or “right”)\n\nStrip labels\nLabels of the facet plots can be modified through the “labels” of the column as a factor, or by the use of a “labeller”.\nMake a labeller like this, using the function as_labeller() from ggplot2. Then provide the labeller to the labeller = argument of facet_wrap() as shown below.\n\nmy_labels <- as_labeller(c(\n \"0-4\" = \"Ages 0-4\",\n \"5-9\" = \"Ages 5-9\",\n \"10-14\" = \"Ages 10-14\",\n \"15-19\" = \"Ages 15-19\",\n \"20-29\" = \"Ages 20-29\",\n \"30-49\" = \"Ages 30-49\",\n \"50-69\" = \"Ages 50-69\",\n \"70+\" = \"Over age 70\"))\n\nAn example facetted plot - facetted by column age_cat.\n\n# make plot\n###########\nggplot(central_data) + \n \n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = age_cat,\n fill = age_cat), # arguments inside aes() apply by group\n \n color = \"black\", # arguments outside aes() apply to all data\n \n # histogram breaks\n breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page)\n closed = \"left\" # count cases from start of breakpoint\n )+ \n \n # The labels on the x-axis\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"2 months\", # labels appear every 2 months\n date_minor_breaks = \"1 month\", # vertical lines appear every 1 month \n label = scales::label_date_short())+ # label formatting\n \n # y-axis\n scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space between bottom of bars and the labels\n \n # aesthetic themes\n theme_minimal()+ # a set of themes to simplify plot\n theme(\n plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"),\n legend.position = \"bottom\",\n strip.text = element_text(face = \"bold\", size = 10),\n strip.background = element_rect(fill = \"grey\"))+ # axis titles in bold\n \n # create facets\n facet_wrap(\n ~age_cat,\n ncol = 4,\n strip.position = \"top\",\n labeller = my_labels)+ \n \n # labels\n labs(\n title = \"Weekly incidence of cases, by age category\",\n subtitle = \"Subtitle\",\n fill = \"Age category\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))\n\n\n\n\n\n\n\n\nSee this link for more information on labellers.\n\nTotal epidemic in facet background\nTo show the total epidemic in the background of each facet, add the function gghighlight() with empty parentheses to the ggplot. This is from the package gghighlight. Note that the y-axis maximum in all facets is now based on the peak of the entire epidemic. There are more examples of this package in the ggplot tips page.\n\nggplot(central_data) + \n \n # epicurves by group\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = age_cat,\n fill = age_cat), # arguments inside aes() apply by group\n \n color = \"black\", # arguments outside aes() apply to all data\n \n # histogram breaks\n breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page)\n \n closed = \"left\", # count cases from start of breakpoint\n )+ # pre-defined date vector (see top of ggplot section) \n \n # add grey epidemic in background to each facet\n gghighlight::gghighlight()+\n \n # labels on x-axis\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"2 months\", # labels appear every 2 months\n date_minor_breaks = \"1 month\", # vertical lines appear every 1 month \n label = scales::label_date_short())+ # label formatting\n \n # y-axis\n scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space below 0\n \n # aesthetic themes\n theme_minimal()+ # a set of themes to simplify plot\n theme(\n plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"),\n legend.position = \"bottom\",\n strip.text = element_text(face = \"bold\", size = 10),\n strip.background = element_rect(fill = \"white\"))+ # axis titles in bold\n \n # create facets\n facet_wrap(\n ~age_cat, # each plot is one value of age_cat\n ncol = 4, # number of columns\n strip.position = \"top\", # position of the facet title/strip\n labeller = my_labels)+ # labeller defines above\n \n # labels\n labs(\n title = \"Weekly incidence of cases, by age category\",\n subtitle = \"Subtitle\",\n fill = \"Age category\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))\n\n\n\n\n\n\n\n\n\n\nOne facet with data\nIf you want to have one facet box that contains all the data, duplicate the entire dataset and treat the duplicates as one faceting value. A “helper” function CreateAllFacet() below can assist with this (thanks to this blog post). When it is run, the number of rows doubles and there will be a new column called facet in which the duplicated rows will have the value “all”, and the original rows have the their original value of the faceting colum. Now you just have to facet on the facet column.\nHere is the helper function. Run it so that it is available to you.\n\n# Define helper function\nCreateAllFacet <- function(df, col){\n df$facet <- df[[col]]\n temp <- df\n temp$facet <- \"all\"\n merged <-rbind(temp, df)\n \n # ensure the facet value is a factor\n merged[[col]] <- as.factor(merged[[col]])\n \n return(merged)\n}\n\nNow apply the helper function to the dataset, on column age_cat:\n\n# Create dataset that is duplicated and with new column \"facet\" to show \"all\" age categories as another facet level\ncentral_data2 <- CreateAllFacet(central_data, col = \"age_cat\") %>%\n \n # set factor levels\n mutate(facet = fct_relevel(facet, \"all\", \"0-4\", \"5-9\",\n \"10-14\", \"15-19\", \"20-29\",\n \"30-49\", \"50-69\", \"70+\"))\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `facet = fct_relevel(...)`.\nCaused by warning:\n! 1 unknown level in `f`: 70+\n\n# check levels\ntable(central_data2$facet, useNA = \"always\")\n\n\n all 0-4 5-9 10-14 15-19 20-29 30-49 50-69 <NA> \n 454 84 84 82 58 73 57 7 9 \n\n\nNotable changes to the ggplot() command are:\n\nThe data used is now central_data2 (double the rows, with new column “facet”)\nLabeller will need to be updated, if used\n\nOptional: to achieve vertically stacked facets: the facet column is moved to rows side of equation and on right is replaced by “.” (facet_wrap(facet~.)), and ncol = 1. You may also need to adjust the width and height of the saved png plot image (see ggsave() in ggplot tips).\n\n\nggplot(central_data2) + \n \n # actual epicurves by group\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = age_cat,\n fill = age_cat), # arguments inside aes() apply by group\n color = \"black\", # arguments outside aes() apply to all data\n \n # histogram breaks\n breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page)\n \n closed = \"left\", # count cases from start of breakpoint\n )+ # pre-defined date vector (see top of ggplot section)\n \n # Labels on x-axis\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"2 months\", # labels appear every 2 months\n date_minor_breaks = \"1 month\", # vertical lines appear every 1 month \n label = scales::label_date_short())+ # automatic label formatting\n \n # y-axis\n scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space between bottom of bars and the labels\n \n # aesthetic themes\n theme_minimal()+ # a set of themes to simplify plot\n theme(\n plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"),\n legend.position = \"bottom\")+ \n \n # create facets\n facet_wrap(facet~. , # each plot is one value of facet\n ncol = 1)+ \n\n # labels\n labs(title = \"Weekly incidence of cases, by age category\",\n subtitle = \"Subtitle\",\n fill = \"Age category\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))", + "text": "32.2 Epicurves with ggplot2\nUsing ggplot() to build your epicurve allows for flexibility and customization, but requires more effort and understanding of how ggplot() works.\nYou must manually control the aggregation of the cases by time (into weeks, months, etc) and the intervals of the labels on the date axis. This must be carefully managed.\nThese examples use a subset of the linelist dataset - only the cases from Central Hospital.\n\n\nWarning: Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\n\n\n\ncentral_data <- linelist %>% \n filter(hospital == \"Central Hospital\")\n\nTo produce an epicurve with ggplot() there are three main elements:\n\nA histogram, with linelist cases aggregated into “bins” distinguished by specific “break” points\nScales for the axes and their labels\nThemes for the plot appearance, including titles, labels, captions, etc\n\n\nSpecify case bins\nHere we show how to specify how cases will be aggregated into histogram bins (“bars”). It is important to recognize that the aggregation of cases into histogram bins is not necessarily the same intervals as the dates that will appear on the x-axis.\nBelow is perhaps the most simple code to produce daily and weekly epicurves.\nIn the over-arching ggplot() command the dataset is provided to data =. Onto this foundation, the geometry of a histogram is added with a +. Within the geom_histogram(), we map the aesthetics such that the column date_onset is mapped to the x-axis. Also within the geom_histogram() but not within aes() we set the binwidth = of the histogram bins, in days. If this ggplot2 syntax is confusing, review the page on ggplot basics.\nCAUTION: Plotting weekly cases by using binwidth = 7 starts the first 7-day bin at the first case, which could be any day of the week! To create specific weeks, see section below .\n\n# daily \nggplot(data = central_data) + # set data\n geom_histogram( # add histogram\n mapping = aes(x = date_onset), # map date column to x-axis\n binwidth = 1) + # cases binned by 1 day \n labs(title = \"Central Hospital - Daily\") # title\n\n# weekly\nggplot(data = central_data) + # set data \n geom_histogram( # add histogram\n mapping = aes(x = date_onset), # map date column to x-axis\n binwidth = 7) + # cases binned every 7 days, starting from first case (!) \n labs(title = \"Central Hospital - 7-day bins, starting at first case\") # title\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLet us note that the first case in this Central Hospital dataset had symptom onset on:\n\nformat(min(central_data$date_onset, na.rm=T), \"%A %d %b, %Y\")\n\n[1] \"Thursday 01 May, 2014\"\n\n\nTo manually specify the histogram bin breaks, do not use the binwidth = argument, and instead supply a vector of dates to breaks =.\nCreate the vector of dates with the base R function seq.Date(). This function expects arguments to =, from =, and by =. For example, the command below returns monthly dates starting at Jan 15 and ending by June 28.\n\nmonthly_breaks <- seq.Date(from = as.Date(\"2014-02-01\"),\n to = as.Date(\"2015-07-15\"),\n by = \"months\")\n\nmonthly_breaks # print\n\n [1] \"2014-02-01\" \"2014-03-01\" \"2014-04-01\" \"2014-05-01\" \"2014-06-01\"\n [6] \"2014-07-01\" \"2014-08-01\" \"2014-09-01\" \"2014-10-01\" \"2014-11-01\"\n[11] \"2014-12-01\" \"2015-01-01\" \"2015-02-01\" \"2015-03-01\" \"2015-04-01\"\n[16] \"2015-05-01\" \"2015-06-01\" \"2015-07-01\"\n\n\nThis vector can be provided to geom_histogram() as breaks =:\n\n# monthly \nggplot(data = central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n breaks = monthly_breaks) + # provide the pre-defined vector of breaks \n labs(title = \"Monthly case bins\") # title\n\n\n\n\n\n\n\n\nA simple weekly date sequence can be returned by setting by = \"week\". For example:\n\nweekly_breaks <- seq.Date(from = as.Date(\"2014-02-01\"),\n to = as.Date(\"2015-07-15\"),\n by = \"week\")\n\nAn alternative to supplying specific start and end dates is to write dynamic code so that weekly bins begin the Monday before the first case. We will use these date vectors throughout the examples below.\n\n# Sequence of weekly Monday dates for CENTRAL HOSPITAL\nweekly_breaks_central <- seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm = T), \"week\", week_start = 1), # monday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm = T), \"week\", week_start = 1), # monday after last case\n by = \"week\")\n\nLet’s unpack the rather daunting code above:\n\nThe “from” value (earliest date of the sequence) is created as follows: the minimum date value (min() with na.rm=TRUE) in the column date_onset is fed to floor_date() from the lubridate package. floor_date() set to “week” returns the start date of that cases’s “week”, given that the start day of each week is a Monday (week_start = 1)\nLikewise, the “to” value (end date of the sequence) is created using the inverse function ceiling_date() to return the Monday after the last case.\nThe “by” argument of seq.Date() can be set to any number of days, weeks, or months\n\nUse week_start = 7 for Sunday weeks\n\nAs we will use these date vectors throughout this page, we also define one for the whole outbreak (the above is for Central Hospital only).\n\n# Sequence for the entire outbreak\nweekly_breaks_all <- seq.Date(\n from = floor_date(min(linelist$date_onset, na.rm=T), \"week\", week_start = 1), # monday before first case\n to = ceiling_date(max(linelist$date_onset, na.rm=T), \"week\", week_start = 1), # monday after last case\n by = \"week\")\n\nThese seq.Date() outputs can be used to create histogram bin breaks, but also the breaks for the date labels, which may be independent from the bins. Read more about the date labels in later sections.\nTIP: For a more simple ggplot() command, save the bin breaks and date label breaks as named vectors in advance, and simply provide their names to breaks =.\n\n\nWeekly epicurve example\nBelow is detailed example code to produce weekly epicurves for Monday weeks, with aligned bars, date labels, and vertical gridlines. This section is for the user who needs code quickly. To understand each aspect (themes, date labels, etc.) in-depth, continue to the subsequent sections. Of note:\n\nThe histogram bin breaks are defined with seq.Date() as explained above to begin the Monday before the earliest case and to end the Monday after the last case\nThe interval of date labels is specified by date_breaks = within scale_x_date()\nThe interval of minor vertical gridlines between date labels is specified to date_minor_breaks =\nWe use closed = \"left\" in the geom_histogram() to ensure the date are counted in the correct bins\nexpand = c(0,0) in the x and y scales removes excess space on each side of the axes, which also ensures the date labels begin from the first bar\n\n\n# TOTAL MONDAY WEEK ALIGNMENT\n#############################\n# Define sequence of weekly breaks\nweekly_breaks_central <- seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm = T), \"week\", week_start = 1), # Monday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm = T), \"week\", week_start = 1), # Monday after last case\n by = \"week\") # bins are 7-days \n\n\nggplot(data = central_data) + \n \n # make histogram: specify bin break points: starts the Monday before first case, end Monday after last case\n geom_histogram(\n \n # mapping aesthetics\n mapping = aes(x = date_onset), # date column mapped to x-axis\n \n # histogram bin breaks\n breaks = weekly_breaks_central, # histogram bin breaks defined previously\n \n closed = \"left\", # count cases from start of breakpoint\n \n # bars\n color = \"darkblue\", # color of lines around bars\n fill = \"lightblue\" # color of fill within bars\n ) + \n \n # x-axis labels\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space before and after case bars\n date_breaks = \"4 weeks\", # date labels and major vertical gridlines appear every 3 Monday weeks\n date_minor_breaks = \"week\", # minor vertical lines appear every Monday week\n date_labels = \"%a\\n%d %b\\n%Y\") + # date labels format\n \n # y-axis\n scale_y_continuous(\n expand = c(0,0)) + # remove excess y-axis space below 0 (align histogram flush with x-axis)\n \n # aesthetic themes\n theme_minimal() + # simplify plot background\n \n theme(\n plot.caption = element_text(hjust = 0, # caption on left side\n face = \"italic\"), # caption in italics\n axis.title = element_text(face = \"bold\")) + # axis titles in bold\n \n # labels including dynamic caption\n labs(\n title = \"Weekly incidence of cases (Monday weeks)\",\n subtitle = \"Note alignment of bars, vertical gridlines, and axis labels on Monday weeks\",\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))\n\n\n\n\n\n\n\n\n\nSunday weeks\nTo achieve the above plot for Sunday weeks a few modifications are needed, because the date_breaks = \"weeks\" only work for Monday weeks.\n\nThe break points of the histogram bins must be set to Sundays (week_start = 7)\nWithin scale_x_date(), the similar date breaks should be provided to breaks = and minor_breaks = to ensure the date labels and vertical gridlines align on Sundays\n\nFor example, the scale_x_date() command for Sunday weeks could look like this:\n\nscale_x_date(\n expand = c(0,0),\n \n # specify interval of date labels and major vertical gridlines\n breaks = seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm = T), \"week\", week_start = 7), # Sunday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm = T), \"week\", week_start = 7), # Sunday after last case\n by = \"4 weeks\"),\n \n # specify interval of minor vertical gridline \n minor_breaks = seq.Date(\n from = floor_date(min(central_data$date_onset, na.rm = T), \"week\", week_start = 7), # Sunday before first case\n to = ceiling_date(max(central_data$date_onset, na.rm = T), \"week\", week_start = 7), # Sunday after last case\n by = \"week\"),\n \n # date label format\n #date_labels = \"%a\\n%d %b\\n%Y\") + # day, above month abbrev., above 2-digit year\n label = scales::label_date_short()) + # automatic label formatting\n\n\n\n\nGroup/color by value\nThe histogram bars can be colored by group and “stacked”. To designate the grouping column, make the following changes. See the ggplot basics page for details.\n\nWithin the histogram aesthetic mapping aes(), map the column name to the group = and fill = arguments\n\nRemove any fill = argument outside of aes(), as it will override the one inside\nArguments inside aes() will apply by group, whereas any outside will apply to all bars (e.g. you may still want color = outside, so each bar has the same border)\n\nHere is what the aes() command would look like to group and color the bars by gender:\n\naes(x = date_onset, group = gender, fill = gender)\n\nHere it is applied:\n\nggplot(data = linelist) + # begin with linelist (many hospitals)\n \n # make histogram: specify bin break points: starts the Monday before first case, end Monday after last case\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = hospital, # set data to be grouped by hospital\n fill = hospital), # bar fill (inside color) by hospital\n \n # bin breaks are Monday weeks\n breaks = weekly_breaks_all, # sequence of weekly Monday bin breaks for whole outbreak, defined in previous code \n \n closed = \"left\", # count cases from start of breakpoint\n\n # Color around bars\n color = \"black\")\n\n\n\n\n\n\n\n\n\n\nAdjust colors\n\nTo manually set the fill for each group, use scale_fill_manual() (note: scale_color_manual() is different!)\n\nUse the values = argument to apply a vector of colors\nUse na.value = to specify a color for NA values\nUse the labels = argument to change the text of legend items. To be safe, provide as a named vector like c(\"old\" = \"new\", \"old\" = \"new\") or adjust the values in the data itself\nUse name = to give a proper title to the legend\n\nFor more tips on color scales and palettes, see the page on ggplot basics\n\n\nggplot(data = linelist) + # begin with linelist (many hospitals)\n \n # make histogram\n geom_histogram(\n mapping = aes(x = date_onset,\n group = hospital, # cases grouped by hospital\n fill = hospital), # bar fill by hospital\n \n # bin breaks\n breaks = weekly_breaks_all, # sequence of weekly Monday bin breaks, defined in previous code\n \n closed = \"left\", # count cases from start of breakpoint\n\n # Color around bars\n color = \"black\") + # border color of each bar\n \n # manual specification of colors\n scale_fill_manual(\n values = c(\"black\", \"orange\", \"grey\", \"beige\", \"blue\", \"brown\"),\n labels = c(\"St. Mark's Maternity Hospital (SMMH)\" = \"St. Mark's\"),\n name = \"Hospital\") # specify fill colors (\"values\") - attention to order!\n\n\n\n\n\n\n\n\n\n\nAdjust level order\nThe order in which grouped bars are stacked is best adjusted by classifying the grouping column as class Factor. You can then designate the factor level order (and their display labels). See the page on Factors or ggplot tips for details.\nBefore making the plot, use the fct_relevel() function from forcats package to convert the grouping column to class factor and manually adjust the level order, as detailed in the page on Factors.\n\n# load forcats package for working with factors\npacman::p_load(forcats)\n\n# Define new dataset with hospital as factor\nplot_data <- linelist %>% \n mutate(hospital = fct_relevel(hospital, c(\"Missing\", \"Other\"))) # Convert to factor and set \"Missing\" and \"Other\" as top levels to appear on epicurve top\n\nlevels(plot_data$hospital) # print levels in order\n\n[1] \"Missing\" \n[2] \"Other\" \n[3] \"Central Hospital\" \n[4] \"Military Hospital\" \n[5] \"Port Hospital\" \n[6] \"St. Mark's Maternity Hospital (SMMH)\"\n\n\nIn the below plot, the only differences from previous is that column hospital has been consolidated as above, and we use guides() to reverse the legend order, so that “Missing” is on the bottom of the legend.\n\nggplot(plot_data) + # Use NEW dataset with hospital as re-ordered factor\n \n # make histogram\n geom_histogram(\n mapping = aes(x = date_onset,\n group = hospital, # cases grouped by hospital\n fill = hospital), # bar fill (color) by hospital\n \n breaks = weekly_breaks_all, # sequence of weekly Monday bin breaks for whole outbreak, defined at top of ggplot section\n \n closed = \"left\", # count cases from start of breakpoint\n\n color = \"black\") + # border color around each bar\n \n # x-axis labels\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space before and after case bars\n date_breaks = \"3 weeks\", # labels appear every 3 Monday weeks\n date_minor_breaks = \"week\", # vertical lines appear every Monday week\n label = scales::label_date_short()) + # efficient label formatting\n \n # y-axis\n scale_y_continuous(\n expand = c(0,0)) + # remove excess y-axis space below 0\n \n # manual specification of colors, ! attention to order\n scale_fill_manual(\n values = c(\"grey\", \"beige\", \"black\", \"orange\", \"blue\", \"brown\"),\n labels = c(\"St. Mark's Maternity Hospital (SMMH)\" = \"St. Mark's\"),\n name = \"Hospital\") + \n \n # aesthetic themes\n theme_minimal() + # simplify plot background\n \n theme(\n plot.caption = element_text(face = \"italic\", # caption on left side in italics\n hjust = 0), \n axis.title = element_text(face = \"bold\")) + # axis titles in bold\n \n # labels\n labs(\n title = \"Weekly incidence of cases by hospital\",\n subtitle = \"Hospital as re-ordered factor\",\n x = \"Week of symptom onset\",\n y = \"Weekly cases\")\n\n\n\n\n\n\n\n\nTIP: To reverse the order of the legend only, add this ggplot2 command: guides(fill = guide_legend(reverse = TRUE)).\n\n\nAdjust legend\nRead more about legends and scales in the ggplot tips page. Here are a few highlights:\n\nEdit legend title either in the scale function or with labs(fill = \"Legend title\") (if your are using color = aesthetic, then use labs(color = \"\"))\n\ntheme(legendtitle = element_blank()) to have no legend title\n\ntheme(legendposition = \"top\") (“bottom”, “left”, “right”, or “none” to remove the legend)\ntheme(legenddirection = \"horizontal\") horizontal legend\nguides(fill = guide_legend(reverse = TRUE)) to reverse order of the legend\n\n\n\nBars side-by-side\nSide-by-side display of group bars (as opposed to stacked) is specified within geom_histogram() with position = \"dodge\" placed outside of aes().\nIf there are more than two value groups, these can become difficult to read. Consider instead using a faceted plot (small multiples). To improve readability in this example, missing gender values are removed.\n\nggplot(central_data %>% \n drop_na(gender)) + # begin with Central Hospital cases dropping missing gender\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = gender, # cases grouped by gender\n fill = gender), # bars filled by gender\n \n # histogram bin breaks\n breaks = weekly_breaks_central, # sequence of weekly dates for Central outbreak - defined at top of ggplot section\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"black\", # bar edge color\n \n position = \"dodge\") + # SIDE-BY-SIDE bars\n \n \n # The labels on the x-axis\n scale_x_date(expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"3 weeks\", # labels appear every 3 Monday weeks\n date_minor_breaks = \"week\", # vertical lines appear every Monday week\n label = scales::label_date_short()) + # efficient date labels\n \n # y-axis\n scale_y_continuous(expand = c(0,0)) + # removes excess y-axis space between bottom of bars and the labels\n \n #scale of colors and legend labels\n scale_fill_manual(values = c(\"brown\", \"orange\"), # specify fill colors (\"values\") - attention to order!\n na.value = \"grey\" ) + \n\n # aesthetic themes\n theme_minimal() + # a set of themes to simplify plot\n theme(plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\")) + # axis titles in bold\n \n # labels\n labs(title = \"Weekly incidence of cases, by gender\",\n subtitle = \"Subtitle\",\n fill = \"Gender\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\")\n\n\n\n\n\n\n\n\n\n\nAxis limits\nThere are two ways to limit the extent of axis values.\nGenerally the preferred way is to use the command coord_cartesian(), which accepts xlim = c(min, max) and ylim = c(min, max) (where you provide the min and max values). This acts as a “zoom” without actually removing any data, which is important for statistics and summary measures.\nAlternatively, you can set maximum and minimum date values using limits = c() within scale_x_date(). For example:\n\nscale_x_date(limits = c(as.Date(\"2014-04-01\"), NA)) # sets a minimum date but leaves the maximum open. \n\nLikewise, if you want to the x-axis to extend to a specific date (e.g. current date), even if no new cases have been reported, you can use:\n\nscale_x_date(limits = c(NA, Sys.Date()) # ensures date axis will extend until current date \n\nDANGER: Be cautious setting the y-axis scale breaks or limits (e.g. 0 to 30 by 5: seq(0, 30, 5)). Such static numbers can cut-off your plot too short if the data changes to exceed the limit!.\n\n\nDate-axis labels/gridlines\nTIP: Remember that date-axis labels are independent from the aggregation of the data into bars, but visually it can be important to align bins, date labels, and vertical grid lines.\nTo modify the date labels and grid lines, use scale_x_date() in one of these ways:\n\nIf your histogram bins are days, Monday weeks, months, or years:\n\nUse date_breaks = to specify the interval of labels and major gridlines (eg “day”, “week”, “3 weeks”, “month”, or “year”)\nUse date_minor_breaks = to specify interval of minor vertical gridlines (between date labels)\n\nAdd expand = c(0,0) to begin the labels at the first bar\n\nUse date_labels = to specify format of date labels - see the Dates page for tips (use \\n for a new line)\n\n\nIf your histogram bins are Sunday weeks:\n\nUse breaks = and minor_breaks = by providing a sequence of date breaks for each\nYou can still use date_labels = and expand = for formatting as described above\n\n\nSome notes:\n\nSee the opening ggplot section for instructions on how to create a sequence of dates using seqDate()\n\nSee this page or the Working with dates page for tips on creating date labels\n\n\nDemonstrations\nBelow is a demonstration of plots where the bins and the plot labels/grid lines are aligned and not aligned:\n\n# 7-day bins + Monday labels\n#############################\nggplot(central_data) +\n geom_histogram(\n mapping = aes(x = date_onset),\n binwidth = 7, # 7-day bins with start at first case\n color = \"darkblue\",\n fill = \"lightblue\") +\n \n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"3 weeks\", # Monday every 3 weeks\n date_minor_breaks = \"week\", # Monday weeks\n label = scales::label_date_short()) + # automatic label formatting\n \n scale_y_continuous(\n expand = c(0, 0)) + # remove excess space under x-axis, make flush\n \n labs(\n title = \"MISALIGNED\",\n subtitle = \"! CAUTION: 7-day bars start Thursdays at first case\\nDate labels and gridlines on Mondays\\nNote how ticks don't align with bars\")\n\n\n\n# 7-day bins + Months\n#####################\nggplot(central_data) +\n geom_histogram(\n mapping = aes(x = date_onset),\n binwidth = 7,\n color = \"darkblue\",\n fill = \"lightblue\") +\n \n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"months\", # 1st of month\n date_minor_breaks = \"week\", # Monday weeks\n label = scales::label_date_short()) + # automatic label formatting\n \n scale_y_continuous(\n expand = c(0, 0)) + # remove excess space under x-axis, make flush \n \n labs(\n title = \"MISALIGNED\",\n subtitle = \"! CAUTION: 7-day bars start Thursdays with first case\\nMajor gridlines and date labels at 1st of each month\\nMinor gridlines weekly on Mondays\\nNote uneven spacing of some gridlines and ticks unaligned with bars\")\n\n\n# TOTAL MONDAY ALIGNMENT: specify manual bin breaks to be mondays\n#################################################################\nggplot(central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n \n # histogram breaks set to 7 days beginning Monday before first case\n breaks = weekly_breaks_central, # defined earlier in this page\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"darkblue\",\n \n fill = \"lightblue\") + \n \n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"4 weeks\", # Monday every 4 weeks\n date_minor_breaks = \"week\", # Monday weeks \n label = scales::label_date_short()) + # label formatting\n \n scale_y_continuous(\n expand = c(0, 0)) + # remove excess space under x-axis, make flush \n \n labs(\n title = \"ALIGNED Mondays\",\n subtitle = \"7-day bins manually set to begin Monday before first case (28 Apr)\\nDate labels and gridlines on Mondays as well\")\n\n\n# TOTAL MONDAY ALIGNMENT WITH MONTHS LABELS:\n############################################\nggplot(central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n \n # histogram breaks set to 7 days beginning Monday before first case\n breaks = weekly_breaks_central, # defined earlier in this page\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"darkblue\",\n \n fill = \"lightblue\") + \n \n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"months\", # Monday every 4 weeks\n date_minor_breaks = \"week\", # Monday weeks \n label = scales::label_date_short()) + # label formatting\n \n scale_y_continuous(\n expand = c(0, 0)) + # remove excess space under x-axis, make flush \n \n theme(panel.grid.major = element_blank()) + # Remove major gridlines (fall on 1st of month)\n \n labs(\n title = \"ALIGNED Mondays with MONTHLY labels\",\n subtitle = \"7-day bins manually set to begin Monday before first case (28 Apr)\\nDate labels on 1st of Month\\nMonthly major gridlines removed\")\n\n\n# TOTAL SUNDAY ALIGNMENT: specify manual bin breaks AND labels to be Sundays\n############################################################################\nggplot(central_data) + \n geom_histogram(\n mapping = aes(x = date_onset),\n \n # histogram breaks set to 7 days beginning Sunday before first case\n breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n by = \"7 days\"),\n \n closed = \"left\", # count cases from start of breakpoint\n\n color = \"darkblue\",\n \n fill = \"lightblue\") + \n \n scale_x_date(\n expand = c(0, 0),\n # date label breaks and major gridlines set to every 3 weeks beginning Sunday before first case\n breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n by = \"3 weeks\"),\n \n # minor gridlines set to weekly beginning Sunday before first case\n minor_breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n to = ceiling_date(max(central_data$date_onset, na.rm=T), \"week\", week_start = 7),\n by = \"7 days\"),\n \n label = scales::label_date_short()) + # label formatting\n \n scale_y_continuous(\n expand = c(0, 0)) + # remove excess space under x-axis, make flush \n \n labs(title = \"ALIGNED Sundays\",\n subtitle = \"7-day bins manually set to begin Sunday before first case (27 Apr)\\nDate labels and gridlines manually set to Sundays as well\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAggregated data\nOften instead of a linelist, you begin with aggregated counts from facilities, districts, etc. You can make an epicurve with ggplot() but the code will be slightly different. This section will utilize the count_data dataset that was imported earlier, in the data preparation section. This dataset is the linelist aggregated to day-hospital counts. The first 50 rows are displayed below.\n\n\n\n\n\n\n\nPlotting daily counts\nWe can plot a daily epicurve from these daily counts. Here are the differences to the code:\n\nWithin the aesthetic mapping aes(), specify y = as the counts column (in this case, the column name is n_cases)\nAdd the argument stat = \"identity\" within geom_histogram(), which specifies that bar height should be the y = value, not the number of rows as is the default\n\nAdd the argument width = to avoid vertical white lines between the bars For daily data set to 1 For weekly count data set to 7 For monthly count data, white lines are an issue (each month has different number of days) - consider transforming your x-axis to a categorical ordered factor (months) and using geom_col()\n\n\nggplot(data = count_data) +\n geom_histogram(\n mapping = aes(x = date_hospitalisation, \n y = n_cases),\n stat = \"identity\",\n width = 1) + # for daily counts, set width = 1 to avoid white space between bars\n labs(\n x = \"Date of report\", \n y = \"Number of cases\",\n title = \"Daily case incidence, from daily count data\")\n\n\n\n\n\n\n\n\n\n\nPlotting weekly counts\nIf your data are already case counts by week, they might look like this dataset (called count_data_weekly):\nThe first 50 rows of count_data_weekly are displayed below. You can see that the counts have been aggregated into weeks. Each week is displayed by the first day of the week (Monday by default).\n\n\n\n\n\n\nNow plot so that x = the epiweek column. Remember to add y = the counts column to the aesthetic mapping, and add stat = \"identity\" as explained above.\n\nggplot(data = count_data_weekly) +\n \n geom_histogram(\n mapping = aes(\n x = epiweek, # x-axis is epiweek (as class Date)\n y = n_cases_weekly, # y-axis height in the weekly case counts\n group = hospital, # we are grouping the bars and coloring by hospital\n fill = hospital),\n stat = \"identity\") + # this is also required when plotting count data\n \n # labels for x-axis\n scale_x_date(\n date_breaks = \"2 months\", # labels every 2 months \n date_minor_breaks = \"1 month\", # gridlines every month\n label = scales::label_date_short()) + # label formatting\n \n # Choose color palette (uses RColorBrewer package)\n scale_fill_brewer(palette = \"Pastel2\") + \n \n theme_minimal() +\n \n labs(\n x = \"Week of onset\", \n y = \"Weekly case incidence\",\n fill = \"Hospital\",\n title = \"Weekly case incidence, from aggregated count data by hospital\")\n\n\n\n\n\n\n\n\n\n\n\nMoving averages\nSee the page on Moving averages for a detailed description and several options. Below is one option for calculating moving averages with the package slider. In this approach, the moving average is calculated in the dataset prior to plotting:\n\nAggregate the data into counts as necessary (daily, weekly, etc.) (see Grouping data page)\nCreate a new column to hold the moving average, created with slide_index() from slider package\nPlot the moving average as a geom_line() on top of (after) the epicurve histogram\n\nSee the helpful online vignette for the slider package.\n\n# load package\npacman::p_load(slider) # slider used to calculate rolling averages\n\n# make dataset of daily counts and 7-day moving average\n#######################################################\nll_counts_7day <- linelist %>% # begin with linelist\n \n ## count cases by date\n count(date_onset, name = \"new_cases\") %>% # name new column with counts as \"new_cases\"\n drop_na(date_onset) %>% # remove cases with missing date_onset\n \n ## calculate the average number of cases in 7-day window\n mutate(\n avg_7day = slider::slide_index( # create new column\n new_cases, # calculate based on value in new_cases column\n .i = date_onset, # index is date_onset col, so non-present dates are included in window \n .f = ~mean(.x, na.rm = TRUE), # function is mean() with missing values removed\n .before = 6, # window is the day and 6-days before\n .complete = FALSE), # must be FALSE for unlist() to work in next step\n avg_7day = unlist(avg_7day)) # convert class list to class numeric\n\n\n# plot\n######\nggplot(data = ll_counts_7day) + # begin with new dataset defined above \n geom_histogram( # create epicurve histogram\n mapping = aes(\n x = date_onset, # date column as x-axis\n y = new_cases), # height is number of daily new cases\n stat = \"identity\", # height is y value\n fill=\"#92a8d1\", # cool color for bars\n colour = \"#92a8d1\", # same color for bar border\n ) + \n geom_line( # make line for rolling average\n mapping = aes(\n x = date_onset, # date column for x-axis\n y = avg_7day, # y-value set to rolling average column\n lty = \"7-day \\nrolling avg\"), # name of line in legend\n color=\"red\", # color of line\n size = 1) + # width of line\n scale_x_date( # date scale\n date_breaks = \"1 month\",\n label = scales::label_date_short(), # label formatting\n expand = c(0,0)) +\n scale_y_continuous( # y-axis scale\n expand = c(0,0),\n limits = c(0, NA)) + \n labs(\n x=\"\",\n y =\"Number of confirmed cases\",\n fill = \"Legend\") + \n theme_minimal() +\n theme(legend.title = element_blank()) # removes title of legend\n\n\n\n\n\n\n\n\n\n\nFaceting/small-multiples\nAs with other ggplots, you can create facetted plots (“small multiples”). As explained in the ggplot tips page of this handbook, you can use either facet_wrap() or facet_grid(). Here we demonstrate with facet_wrap(). For epicurves, facet_wrap() is typically easier as it is likely that you only need to facet on one column.\nThe general syntax is facet_wrap(rows ~ cols), where to the left of the tilde (~) is the name of a column to be spread across the “rows” of the facetted plot, and to the right of the tilde is the name of a column to be spread across the “columns” of the facetted plot. Most simply, just use one column name, to the right of the tilde: facet_wrap(~age_cat).\nFree axes\nYou will need to decide whether the scales of the axes for each facet are “fixed” to the same dimensions (default), or “free” (meaning they will change based on the data within the facet). Do this with the scales = argument within facet_wrap() by specifying “free_x” or “free_y”, or “free”.\nNumber of cols and rows of facets\nThis can be specified with ncol = and nrow = within facet_wrap().\nOrder of panels\nTo change the order of appearance, change the underlying order of the levels of the factor column used to create the facets.\nAesthetics\nFont size and face, strip color, etc. can be modified through theme() with arguments like:\n\nstriptext = element_text() (size, colour, face, angle, etc)\nstripbackground = element_rect() (eg element_rect(fill = “grey”))\n\nstripposition = (position of the strip “bottom”, “top”, “left”, or “right”)\n\nStrip labels\nLabels of the facet plots can be modified through the “labels” of the column as a factor, or by the use of a “labeller”.\nMake a labeller like this, using the function as_labeller() from ggplot2. Then provide the labeller to the labeller = argument of facet_wrap() as shown below.\n\nmy_labels <- as_labeller(c(\n \"0-4\" = \"Ages 0-4\",\n \"5-9\" = \"Ages 5-9\",\n \"10-14\" = \"Ages 10-14\",\n \"15-19\" = \"Ages 15-19\",\n \"20-29\" = \"Ages 20-29\",\n \"30-49\" = \"Ages 30-49\",\n \"50-69\" = \"Ages 50-69\",\n \"70+\" = \"Over age 70\"))\n\nAn example facetted plot - facetted by column age_cat.\n\n# make plot\n###########\nggplot(central_data) + \n \n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = age_cat,\n fill = age_cat), # arguments inside aes() apply by group\n \n color = \"black\", # arguments outside aes() apply to all data\n \n # histogram breaks\n breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page)\n closed = \"left\" # count cases from start of breakpoint\n ) + \n \n # The labels on the x-axis\n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"2 months\", # labels appear every 2 months\n date_minor_breaks = \"1 month\", # vertical lines appear every 1 month \n label = scales::label_date_short()) + # label formatting\n \n # y-axis\n scale_y_continuous(expand = c(0, 0)) + # removes excess y-axis space between bottom of bars and the labels\n \n # aesthetic themes\n theme_minimal() + # a set of themes to simplify plot\n theme(\n plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"),\n legend.position = \"bottom\",\n strip.text = element_text(face = \"bold\", size = 10),\n strip.background = element_rect(fill = \"grey\")) + # axis titles in bold\n \n # create facets\n facet_wrap(\n ~age_cat,\n ncol = 4,\n strip.position = \"top\",\n labeller = my_labels) + \n \n # labels\n labs(\n title = \"Weekly incidence of cases, by age category\",\n subtitle = \"Subtitle\",\n fill = \"Age category\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))\n\n\n\n\n\n\n\n\nSee this link for more information on labellers.\n\nTotal epidemic in facet background\nTo show the total epidemic in the background of each facet, add the function gghighlight() with empty parentheses to the ggplot. This is from the package gghighlight. Note that the y-axis maximum in all facets is now based on the peak of the entire epidemic. There are more examples of this package in the ggplot tips page.\n\nggplot(central_data) + \n \n # epicurves by group\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = age_cat,\n fill = age_cat), # arguments inside aes() apply by group\n \n color = \"black\", # arguments outside aes() apply to all data\n \n # histogram breaks\n breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page)\n \n closed = \"left\", # count cases from start of breakpoint\n ) + # pre-defined date vector (see top of ggplot section) \n \n # add grey epidemic in background to each facet\n gghighlight::gghighlight() +\n \n # labels on x-axis\n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"2 months\", # labels appear every 2 months\n date_minor_breaks = \"1 month\", # vertical lines appear every 1 month \n label = scales::label_date_short()) + # label formatting\n \n # y-axis\n scale_y_continuous(expand = c(0, 0)) + # removes excess y-axis space below 0\n \n # aesthetic themes\n theme_minimal() + # a set of themes to simplify plot\n theme(\n plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"),\n legend.position = \"bottom\",\n strip.text = element_text(face = \"bold\", size = 10),\n strip.background = element_rect(fill = \"white\")) + # axis titles in bold\n \n # create facets\n facet_wrap(\n ~age_cat, # each plot is one value of age_cat\n ncol = 4, # number of columns\n strip.position = \"top\", # position of the facet title/strip\n labeller = my_labels) + # labeller defines above\n \n # labels\n labs(\n title = \"Weekly incidence of cases, by age category\",\n subtitle = \"Subtitle\",\n fill = \"Age category\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))\n\n\n\n\n\n\n\n\n\n\nOne facet with data\nIf you want to have one facet box that contains all the data, duplicate the entire dataset and treat the duplicates as one faceting value. A “helper” function CreateAllFacet() below can assist with this (thanks to this blog post). When it is run, the number of rows doubles and there will be a new column called facet in which the duplicated rows will have the value “all”, and the original rows have the their original value of the faceting colum. Now you just have to facet on the facet column.\nHere is the helper function. Run it so that it is available to you.\n\n# Define helper function\nCreateAllFacet <- function(df, col){\n df$facet <- df[[col]]\n temp <- df\n temp$facet <- \"all\"\n merged <-rbind(temp, df)\n \n # ensure the facet value is a factor\n merged[[col]] <- as.factor(merged[[col]])\n \n return(merged)\n}\n\nNow apply the helper function to the dataset, on column age_cat:\n\n# Create dataset that is duplicated and with new column \"facet\" to show \"all\" age categories as another facet level\ncentral_data2 <- CreateAllFacet(central_data, col = \"age_cat\") %>%\n \n # set factor levels\n mutate(facet = fct_relevel(facet, \"all\", \"0-4\", \"5-9\",\n \"10-14\", \"15-19\", \"20-29\",\n \"30-49\", \"50-69\", \"70+\"))\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `facet = fct_relevel(...)`.\nCaused by warning:\n! 1 unknown level in `f`: 70+\n\n# check levels\ntable(central_data2$facet, useNA = \"always\")\n\n\n all 0-4 5-9 10-14 15-19 20-29 30-49 50-69 <NA> \n 454 84 84 82 58 73 57 7 9 \n\n\nNotable changes to the ggplot() command are:\n\nThe data used is now central_data2 (double the rows, with new column “facet”)\nLabeller will need to be updated, if used\n\nOptional: to achieve vertically stacked facets: the facet column is moved to rows side of equation and on right is replaced by “.” (facet_wrap(facet~.)), and ncol = 1. You may also need to adjust the width and height of the saved png plot image (see ggsave() in ggplot tips).\n\n\nggplot(central_data2) + \n \n # actual epicurves by group\n geom_histogram(\n mapping = aes(\n x = date_onset,\n group = age_cat,\n fill = age_cat), # arguments inside aes() apply by group\n color = \"black\", # arguments outside aes() apply to all data\n \n # histogram breaks\n breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page)\n \n closed = \"left\", # count cases from start of breakpoint\n ) + # pre-defined date vector (see top of ggplot section)\n \n # Labels on x-axis\n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"2 months\", # labels appear every 2 months\n date_minor_breaks = \"1 month\", # vertical lines appear every 1 month \n label = scales::label_date_short()) + # automatic label formatting\n \n # y-axis\n scale_y_continuous(expand = c(0, 0)) + # removes excess y-axis space between bottom of bars and the labels\n \n # aesthetic themes\n theme_minimal() + # a set of themes to simplify plot\n theme(\n plot.caption = element_text(face = \"italic\", hjust = 0), # caption on left side in italics\n axis.title = element_text(face = \"bold\"),\n legend.position = \"bottom\") + \n \n # create facets\n facet_wrap(facet~. , # each plot is one value of facet\n ncol = 1) + \n\n # labels\n labs(title = \"Weekly incidence of cases, by age category\",\n subtitle = \"Subtitle\",\n fill = \"Age category\", # provide new title for legend\n x = \"Week of symptom onset\",\n y = \"Weekly incident cases reported\",\n caption = stringr::str_glue(\"n = {nrow(central_data)} from Central Hospital; Case onsets range from {format(min(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')} to {format(max(central_data$date_onset, na.rm=T), format = '%a %d %b %Y')}\\n{nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown\"))", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -3002,7 +3002,7 @@ "href": "new_pages/epicurves.html#tentative-data", "title": "32  Epidemic curves", "section": "32.3 Tentative data", - "text": "32.3 Tentative data\nThe most recent data shown in epicurves should often be marked as tentative, or subject to reporting delays. This can be done in by adding a vertical line and/or rectangle over a specified number of days. Here are two options:\n\nUse annotate():\n\nFor a line use annotate(geom = \"segment\"). Provide x, xend, y, and yend. Adjust size, linetype (lty), and color.\n\nFor a rectangle use annotate(geom = \"rect\"). Provide xmin/xmax/ymin/ymax. Adjust color and alpha.\n\n\nGroup the data by tentative status and color those bars differently\n\nCAUTION: You might try geom_rect() to draw a rectangle, but adjusting the transparency does not work in a linelist context. This function overlays one rectangle for each observation/row!. Use either a very low alpha (e.g. 0.01), or another approach. \n\nUsing annotate()\n\nWithin annotate(geom = \"rect\"), the xmin and xmax arguments must be given inputs of class Date.\n\nNote that because these data are aggregated into weekly bars, and the last bar extends to the Monday after the last data point, the shaded region may appear to cover 4 weeks\n\nHere is an annotate() online example\n\n\nggplot(central_data) + \n \n # histogram\n geom_histogram(\n mapping = aes(x = date_onset),\n \n breaks = weekly_breaks_central, # pre-defined date vector - see top of ggplot section\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"darkblue\",\n \n fill = \"lightblue\") +\n\n # scales\n scale_y_continuous(expand = c(0,0))+\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"1 month\", # 1st of month\n date_minor_breaks = \"1 month\", # 1st of month\n label = scales::label_date_short())+ # automatic label formatting\n \n # labels and theme\n labs(\n title = \"Using annotate()\\nRectangle and line showing that data from last 21-days are tentative\",\n x = \"Week of symptom onset\",\n y = \"Weekly case indicence\")+ \n theme_minimal()+\n \n # add semi-transparent red rectangle to tentative data\n annotate(\n \"rect\",\n xmin = as.Date(max(central_data$date_onset, na.rm = T) - 21), # note must be wrapped in as.Date()\n xmax = as.Date(Inf), # note must be wrapped in as.Date()\n ymin = 0,\n ymax = Inf,\n alpha = 0.2, # alpha easy and intuitive to adjust using annotate()\n fill = \"red\")+\n \n # add black vertical line on top of other layers\n annotate(\n \"segment\",\n x = max(central_data$date_onset, na.rm = T) - 21, # 21 days before last data\n xend = max(central_data$date_onset, na.rm = T) - 21, \n y = 0, # line begins at y = 0\n yend = Inf, # line to top of plot\n size = 2, # line size\n color = \"black\",\n lty = \"solid\")+ # linetype e.g. \"solid\", \"dashed\"\n\n # add text in rectangle\n annotate(\n \"text\",\n x = max(central_data$date_onset, na.rm = T) - 15,\n y = 15,\n label = \"Subject to reporting delays\",\n angle = 90)\n\n\n\n\n\n\n\n\nThe same black vertical line can be achieved with the code below, but using geom_vline() you lose the ability to control the height:\n\ngeom_vline(xintercept = max(central_data$date_onset, na.rm = T) - 21,\n size = 2,\n color = \"black\")\n\n\n\nBars color\nAn alternative approach could be to adjust the color or display of the tentative bars of data themselves. You could create a new column in the data preparation stage and use it to group the data, such that the aes(fill = ) of tentative data can be a different color or alpha than the other bars.\n\n# add column\n############\nplot_data <- central_data %>% \n mutate(tentative = case_when(\n date_onset >= max(date_onset, na.rm=T) - 7 ~ \"Tentative\", # tenative if in last 7 days\n TRUE ~ \"Reliable\")) # all else reliable\n\n# plot\n######\nggplot(plot_data, aes(x = date_onset, fill = tentative)) + \n \n # histogram\n geom_histogram(\n breaks = weekly_breaks_central, # pre-defined data vector, see top of ggplot page\n closed = \"left\", # count cases from start of breakpoint\n color = \"black\") +\n\n # scales\n scale_y_continuous(expand = c(0,0))+\n scale_fill_manual(values = c(\"lightblue\", \"grey\"))+\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n date_breaks = \"3 weeks\", # Monday every 3 weeks\n date_minor_breaks = \"week\", # Monday weeks \n label = scales::label_date_short())+ # automatic label formatting\n \n # labels and theme\n labs(title = \"Show days that are tentative reporting\",\n subtitle = \"\")+ \n theme_minimal()+\n theme(legend.title = element_blank()) # remove title of legend", + "text": "32.3 Tentative data\nThe most recent data shown in epicurves should often be marked as tentative, or subject to reporting delays. This can be done in by adding a vertical line and/or rectangle over a specified number of days. Here are two options:\n\nUse annotate():\n\nFor a line use annotate(geom = \"segment\"). Provide x, xend, y, and yend. Adjust size, linetype (lty), and color\nFor a rectangle use annotate(geom = \"rect\"). Provide xmin/xmax/ymin/ymax. Adjust color and alpha.\n\n\nGroup the data by tentative status and color those bars differently\n\nCAUTION: You might try geom_rect() to draw a rectangle, but adjusting the transparency does not work in a linelist context. This function overlays one rectangle for each observation/row!. Use either a very low alpha (e.g. 0.01), or another approach. \n\nUsing annotate()\n\nWithin annotate(geom = \"rect\"), the xmin and xmax arguments must be given inputs of class Date\nNote that because these data are aggregated into weekly bars, and the last bar extends to the Monday after the last data point, the shaded region may appear to cover 4 weeks\nHere is an annotate() online example\n\n\nggplot(central_data) + \n \n # histogram\n geom_histogram(\n mapping = aes(x = date_onset),\n \n breaks = weekly_breaks_central, # pre-defined date vector - see top of ggplot section\n \n closed = \"left\", # count cases from start of breakpoint\n \n color = \"darkblue\",\n \n fill = \"lightblue\") +\n\n # scales\n scale_y_continuous(expand = c(0, 0)) +\n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"1 month\", # 1st of month\n date_minor_breaks = \"1 month\", # 1st of month\n label = scales::label_date_short()) + # automatic label formatting\n \n # labels and theme\n labs(\n title = \"Using annotate()\\nRectangle and line showing that data from last 21-days are tentative\",\n x = \"Week of symptom onset\",\n y = \"Weekly case indicence\") + \n theme_minimal() +\n \n # add semi-transparent red rectangle to tentative data\n annotate(\n \"rect\",\n xmin = as.Date(max(central_data$date_onset, na.rm = T) - 21), # note must be wrapped in as.Date()\n xmax = as.Date(Inf), # note must be wrapped in as.Date()\n ymin = 0,\n ymax = Inf,\n alpha = 0.2, # alpha easy and intuitive to adjust using annotate()\n fill = \"red\") +\n \n # add black vertical line on top of other layers\n annotate(\n \"segment\",\n x = max(central_data$date_onset, na.rm = T) - 21, # 21 days before last data\n xend = max(central_data$date_onset, na.rm = T) - 21, \n y = 0, # line begins at y = 0\n yend = Inf, # line to top of plot\n size = 2, # line size\n color = \"black\",\n lty = \"solid\") + # linetype e.g. \"solid\", \"dashed\"\n\n # add text in rectangle\n annotate(\n \"text\",\n x = max(central_data$date_onset, na.rm = T) - 15,\n y = 15,\n label = \"Subject to reporting delays\",\n angle = 90)\n\n\n\n\n\n\n\n\nThe same black vertical line can be achieved with the code below, but using geom_vline() you lose the ability to control the height:\n\ngeom_vline(xintercept = max(central_data$date_onset, na.rm = T) - 21,\n size = 2,\n color = \"black\")\n\n\n\nBars color\nAn alternative approach could be to adjust the color or display of the tentative bars of data themselves. You could create a new column in the data preparation stage and use it to group the data, such that the aes(fill = ) of tentative data can be a different color or alpha than the other bars.\n\n# add column\n############\nplot_data <- central_data %>% \n mutate(tentative = case_when(\n date_onset >= max(date_onset, na.rm=T) - 7 ~ \"Tentative\", # tenative if in last 7 days\n TRUE ~ \"Reliable\")) # all else reliable\n\n# plot\n######\nggplot(plot_data, aes(x = date_onset, fill = tentative)) + \n \n # histogram\n geom_histogram(\n breaks = weekly_breaks_central, # pre-defined data vector, see top of ggplot page\n closed = \"left\", # count cases from start of breakpoint\n color = \"black\") +\n\n # scales\n scale_y_continuous(expand = c(0, 0)) +\n scale_fill_manual(values = c(\"lightblue\", \"grey\")) +\n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n date_breaks = \"3 weeks\", # Monday every 3 weeks\n date_minor_breaks = \"week\", # Monday weeks \n label = scales::label_date_short()) + # automatic label formatting\n \n # labels and theme\n labs(title = \"Show days that are tentative reporting\",\n subtitle = \"\") + \n theme_minimal() +\n theme(legend.title = element_blank()) # remove title of legend", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -3013,7 +3013,7 @@ "href": "new_pages/epicurves.html#multi-level-date-labels", "title": "32  Epidemic curves", "section": "32.4 Multi-level date labels", - "text": "32.4 Multi-level date labels\nIf you want multi-level date labels (e.g. month and year) without duplicating the lower label levels, consider one of the approaches below:\nRemember - you can can use tools like \\n within the date_labels or labels arguments to put parts of each label on a new line below. However, the codes below help you take years or months (for example) on a lower line and only once.\nThe easiest method is to assign the labels = argument in scale_x_date() to the function label_date_short() from the package scales (note: don’t forget to include empty parentheses (), as shown below). This function will automatically construct efficient date labels (read more here). An additional benefit of this function is that the labels will automatically adjust as your data expands over time: from days, to weeks, to months and years.\n\nggplot(central_data) + \n \n # histogram\n geom_histogram(\n mapping = aes(x = date_onset),\n breaks = weekly_breaks_central, # pre-defined date vector - see top of ggplot section\n closed = \"left\", # count cases from start of breakpoint\n color = \"darkblue\",\n fill = \"lightblue\") +\n\n # y-axis scale as before \n scale_y_continuous(expand = c(0,0))+\n \n # x-axis scale sets efficient date labels\n scale_x_date(\n expand = c(0,0), # remove excess x-axis space below and after case bars\n labels = scales::label_date_short())+ # auto efficient date labels\n \n # labels and theme\n labs(\n title = \"Using label_date_short()\\nTo make automatic and efficient date labels\",\n x = \"Week of symptom onset\",\n y = \"Weekly case indicence\")+ \n theme_minimal()\n\n\n\n\n\n\n\n\nA second option is to use faceting. Below:\n\nCase counts are aggregated into weeks for aesthetic reasons. See Epicurves page (aggregated data tab) for details.\n\nA geom_area() line is used instead of a histogram, as the faceting approach below does not work well with histograms.\n\nAggregate to weekly counts\n\n# Create dataset of case counts by week\n#######################################\ncentral_weekly <- linelist %>%\n filter(hospital == \"Central Hospital\") %>% # filter linelist\n mutate(week = lubridate::floor_date(date_onset, unit = \"weeks\")) %>% \n count(week) %>% # summarize weekly case counts\n drop_na(week) %>% # remove cases with missing onset_date\n complete( # fill-in all weeks with no cases reported\n week = seq.Date(\n from = min(week), \n to = max(week),\n by = \"week\"),\n fill = list(n = 0)) # convert new NA values to 0 counts\n\nMake plots\n\n# plot with no facet box border\n#################################\nggplot(central_weekly,\n aes(x = week, y = n)) + # establish x and y for entire plot\n geom_line(stat = \"identity\", # make line, line height is count number\n color = \"#69b3a2\") + # line color\n geom_point(size=1, color=\"#69b3a2\") + # make points at the weekly data points\n geom_area(fill = \"#69b3a2\", # fill area below line\n alpha = 0.4)+ # fill transparency\n scale_x_date(date_labels=\"%b\", # date label format show month \n date_breaks=\"month\", # date labels on 1st of each month\n expand=c(0,0)) + # remove excess space\n scale_y_continuous(\n expand = c(0,0))+ # remove excess space below x-axis\n facet_grid(~lubridate::year(week), # facet on year (of Date class column)\n space=\"free_x\", \n scales=\"free_x\", # x-axes adapt to data range (not \"fixed\")\n switch=\"x\") + # facet labels (year) on bottom\n theme_bw() +\n theme(strip.placement = \"outside\", # facet label placement\n strip.background = element_blank(), # no facet lable background\n panel.grid.minor.x = element_blank(), \n panel.border = element_blank(), # no border for facet panel\n panel.spacing=unit(0,\"cm\"))+ # No space between facet panels\n labs(title = \"Nested year labels - points, shaded, no label border\")\n\n\n\n\n\n\n\n\nThe above technique for faceting was adapted from this and this post on stackoverflow.com.", + "text": "32.4 Multi-level date labels\nIf you want multi-level date labels (e.g. month and year) without duplicating the lower label levels, consider one of the approaches below:\nRemember - you can can use tools like \\n within the date_labels or labels arguments to put parts of each label on a new line below. However, the codes below help you take years or months (for example) on a lower line and only once.\nThe easiest method is to assign the labels = argument in scale_x_date() to the function label_date_short() from the package scales (note: don’t forget to include empty parentheses (), as shown below). This function will automatically construct efficient date labels (read more here). An additional benefit of this function is that the labels will automatically adjust as your data expands over time: from days, to weeks, to months and years.\n\nggplot(central_data) + \n \n # histogram\n geom_histogram(\n mapping = aes(x = date_onset),\n breaks = weekly_breaks_central, # pre-defined date vector - see top of ggplot section\n closed = \"left\", # count cases from start of breakpoint\n color = \"darkblue\",\n fill = \"lightblue\") +\n\n # y-axis scale as before \n scale_y_continuous(expand = c(0, 0)) +\n \n # x-axis scale sets efficient date labels\n scale_x_date(\n expand = c(0, 0), # remove excess x-axis space below and after case bars\n labels = scales::label_date_short()) + # auto efficient date labels\n \n # labels and theme\n labs(\n title = \"Using label_date_short()\\nTo make automatic and efficient date labels\",\n x = \"Week of symptom onset\",\n y = \"Weekly case indicence\") + \n theme_minimal()\n\n\n\n\n\n\n\n\nA second option is to use faceting. Below:\n\nCase counts are aggregated into weeks for aesthetic reasons. See Epicurves page (aggregated data tab) for details\nA geom_area() line is used instead of a histogram, as the faceting approach below does not work well with histograms\n\nAggregate to weekly counts\n\n# Create dataset of case counts by week\n#######################################\ncentral_weekly <- linelist %>%\n filter(hospital == \"Central Hospital\") %>% # filter linelist\n mutate(week = lubridate::floor_date(date_onset, unit = \"weeks\")) %>% \n count(week) %>% # summarize weekly case counts\n drop_na(week) %>% # remove cases with missing onset_date\n complete( # fill-in all weeks with no cases reported\n week = seq.Date(\n from = min(week), \n to = max(week),\n by = \"week\"),\n fill = list(n = 0)) # convert new NA values to 0 counts\n\nMake plots\n\n# plot with no facet box border\n#################################\nggplot(central_weekly,\n aes(x = week, y = n)) + # establish x and y for entire plot\n geom_line(stat = \"identity\", # make line, line height is count number\n color = \"#69b3a2\") + # line color\n geom_point(size=1, color=\"#69b3a2\") + # make points at the weekly data points\n geom_area(fill = \"#69b3a2\", # fill area below line\n alpha = 0.4) + # fill transparency\n scale_x_date(date_labels=\"%b\", # date label format show month \n date_breaks=\"month\", # date labels on 1st of each month\n expand=c(0, 0)) + # remove excess space\n scale_y_continuous(\n expand = c(0, 0)) + # remove excess space below x-axis\n facet_grid(~lubridate::year(week), # facet on year (of Date class column)\n space=\"free_x\", \n scales=\"free_x\", # x-axes adapt to data range (not \"fixed\")\n switch=\"x\") + # facet labels (year) on bottom\n theme_bw() +\n theme(strip.placement = \"outside\", # facet label placement\n strip.background = element_blank(), # no facet lable background\n panel.grid.minor.x = element_blank(), \n panel.border = element_blank(), # no border for facet panel\n panel.spacing=unit(0,\"cm\")) + # No space between facet panels\n labs(title = \"Nested year labels - points, shaded, no label border\")\n\n\n\n\n\n\n\n\nThe above technique for faceting was adapted from this and this post on stackoverflow.com.", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -3024,7 +3024,7 @@ "href": "new_pages/epicurves.html#dual-axis", "title": "32  Epidemic curves", "section": "32.5 Dual-axis", - "text": "32.5 Dual-axis\nAlthough there are fierce discussions about the validity of dual axes within the data visualization community, many epi supervisors still want to see an epicurve or similar chart with a percent overlaid with a second axis. This is discussed more extensively in the ggplot tips page, but one example using the cowplot method is shown below:\n\nTwo distinct plots are made, and then combined with cowplot package.\n\nThe plots must have the exact same x-axis (set limits) or else the data and labels will not align\n\nEach uses theme_cowplot() and one has the y-axis moved to the right side of the plot\n\n\n#load package\npacman::p_load(cowplot)\n\n# Make first plot of epicurve histogram\n#######################################\nplot_cases <- linelist %>% \n \n # plot cases per week\n ggplot()+\n \n # create histogram \n geom_histogram(\n \n mapping = aes(x = date_onset),\n \n # bin breaks every week beginning monday before first case, going to monday after last case\n breaks = weekly_breaks_all)+ # pre-defined vector of weekly dates (see top of ggplot section)\n \n # specify beginning and end of date axis to align with other plot\n scale_x_date(\n limits = c(min(weekly_breaks_all), max(weekly_breaks_all)))+ # min/max of the pre-defined weekly breaks of histogram\n \n # labels\n labs(\n y = \"Daily cases\",\n x = \"Date of symptom onset\"\n )+\n theme_cowplot()\n\n\n# make second plot of percent died per week\n###########################################\nplot_deaths <- linelist %>% # begin with linelist\n group_by(week = floor_date(date_onset, \"week\")) %>% # create week column\n \n # summarise to get weekly percent of cases who died\n summarise(n_cases = n(),\n died = sum(outcome == \"Death\", na.rm=T),\n pct_died = 100*died/n_cases) %>% \n \n # begin plot\n ggplot()+\n \n # line of weekly percent who died\n geom_line( # create line of percent died\n mapping = aes(x = week, y = pct_died), # specify y-height as pct_died column\n stat = \"identity\", # set line height to the value in pct_death column, not the number of rows (which is default)\n size = 2,\n color = \"black\")+\n \n # Same date-axis limits as the other plot - perfect alignment\n scale_x_date(\n limits = c(min(weekly_breaks_all), max(weekly_breaks_all)))+ # min/max of the pre-defined weekly breaks of histogram\n \n \n # y-axis adjustments\n scale_y_continuous( # adjust y-axis\n breaks = seq(0,100, 10), # set break intervals of percent axis\n limits = c(0, 100), # set extent of percent axis\n position = \"right\")+ # move percent axis to the right\n \n # Y-axis label, no x-axis label\n labs(x = \"\",\n y = \"Percent deceased\")+ # percent axis label\n \n theme_cowplot() # add this to make the two plots merge together nicely\n\nNow use cowplot to overlay the two plots. Attention has been paid to the x-axis alignment, side of the y-axis, and use of theme_cowplot().\n\naligned_plots <- cowplot::align_plots(plot_cases, plot_deaths, align=\"hv\", axis=\"tblr\")\nggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]])", + "text": "32.5 Dual-axis\nAlthough there are fierce discussions about the validity of dual axes within the data visualization community, many epi supervisors still want to see an epicurve or similar chart with a percent overlaid with a second axis. This is discussed more extensively in the ggplot tips page, but one example using the argument sec_axis = of the function scale_y_continuous().\nThis walkthrough can be found in more detail in ggplot tips.\n\n#Set up linelist for primary axis - the weekly cases epicurve\nlinelist_primary_axis <- linelist %>% \n count(epiweek = lubridate::floor_date(date_onset, \"week\"))\n\n#Set up linelist for secondary axis - the line graph of the weekly percent of deaths\nlinelist_secondary_axis <- linelist %>% \n group_by(\n epiweek = lubridate::floor_date(date_onset, \"week\")) %>% \n summarise(\n n = n(),\n pct_death = 100*sum(outcome == \"Death\", na.rm = T) / n)\n\n#Set up scaling factor to transform secondary axis\nlinelist_primary_axis_max <- linelist_primary_axis %>%\n pull(n) %>%\n max()\n\nlinelist_secondary_axis_max <- linelist_secondary_axis %>%\n pull(pct_death) %>%\n max()\n\n#Create our scaling factor, how much the secondary axis value must be divided by to create values on the same scale as the primary axis\nscaling_factor <- linelist_secondary_axis_max/linelist_primary_axis_max\n\nggplot() +\n #First create the epicurve\n geom_area(data = linelist_primary_axis,\n mapping = aes(x = epiweek, \n y = n), \n fill = \"grey\"\n ) +\n #Now create the linegraph\n geom_line(data = linelist_secondary_axis,\n mapping = aes(x = epiweek, \n y = pct_death / scaling_factor)\n ) +\n #Now we specify the second axis, and note that we are going to be multiplying the values of the second axis by the scaling factor in order to get the axis to display the correct values\n scale_y_continuous(\n sec.axis = sec_axis(~.*scaling_factor, \n name = \"Weekly percent of deaths\")\n ) +\n scale_x_date(\n date_breaks = \"month\",\n date_labels = \"%b\"\n ) +\n labs(\n x = \"Epiweek of symptom onset\",\n y = \"Weekly cases\",\n title = \"Weekly case incidence and percent deaths\"\n ) +\n theme_bw()", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -3035,7 +3035,7 @@ "href": "new_pages/epicurves.html#cumulative-incidence", "title": "32  Epidemic curves", "section": "32.6 Cumulative Incidence", - "text": "32.6 Cumulative Incidence\nIf beginning with a case linelist, create a new column containing the cumulative number of cases per day in an outbreak using cumsum() from base R:\n\ncumulative_case_counts <- linelist %>% \n count(date_onset) %>% # count of rows per day (returned in column \"n\") \n mutate( \n cumulative_cases = cumsum(n) # new column of the cumulative number of rows at each date\n )\n\nThe first 10 rows are shown below:\n\n\n\n\n\n\nThis cumulative column can then be plotted against date_onset, using geom_line():\n\nplot_cumulative <- ggplot()+\n geom_line(\n data = cumulative_case_counts,\n aes(x = date_onset, y = cumulative_cases),\n size = 2,\n color = \"blue\")\n\nplot_cumulative\n\n\n\n\n\n\n\n\nIt can also be overlaid onto the epicurve, with dual-axis using the cowplot method described above and in the ggplot tips page:\n\n#load package\npacman::p_load(cowplot)\n\n# Make first plot of epicurve histogram\nplot_cases <- ggplot()+\n geom_histogram( \n data = linelist,\n aes(x = date_onset),\n binwidth = 1)+\n labs(\n y = \"Daily cases\",\n x = \"Date of symptom onset\"\n )+\n theme_cowplot()\n\n# make second plot of cumulative cases line\nplot_cumulative <- ggplot()+\n geom_line(\n data = cumulative_case_counts,\n aes(x = date_onset, y = cumulative_cases),\n size = 2,\n color = \"blue\")+\n scale_y_continuous(\n position = \"right\")+\n labs(x = \"\",\n y = \"Cumulative cases\")+\n theme_cowplot()+\n theme(\n axis.line.x = element_blank(),\n axis.text.x = element_blank(),\n axis.title.x = element_blank(),\n axis.ticks = element_blank())\n\nNow use cowplot to overlay the two plots. Attention has been paid to the x-axis alignment, side of the y-axis, and use of theme_cowplot().\n\naligned_plots <- cowplot::align_plots(plot_cases, plot_cumulative, align=\"hv\", axis=\"tblr\")\nggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]])", + "text": "32.6 Cumulative Incidence\nIf beginning with a case linelist, create a new column containing the cumulative number of cases per day in an outbreak using cumsum() from base R:\n\ncumulative_case_counts <- linelist %>% \n count(date_onset) %>% # count of rows per day (returned in column \"n\") \n mutate( \n cumulative_cases = cumsum(n) # new column of the cumulative number of rows at each date\n )\n\nThe first 10 rows are shown below:\n\n\n\n\n\n\nThis cumulative column can then be plotted against date_onset, using geom_line():\n\nplot_cumulative <- ggplot() +\n geom_line(\n data = cumulative_case_counts,\n aes(x = date_onset, y = cumulative_cases),\n size = 2,\n color = \"blue\")\n\nplot_cumulative\n\n\n\n\n\n\n\n\nIt can also be overlaid onto the epicurve, with dual-axis using the method described above and in the ggplot tips page.", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -3045,8 +3045,8 @@ "objectID": "new_pages/epicurves.html#resources", "href": "new_pages/epicurves.html#resources", "title": "32  Epidemic curves", - "section": "32.7 Resources", - "text": "32.7 Resources", + "section": "32.8 Resources", + "text": "32.8 Resources\nincidence2", "crumbs": [ "Data Visualization", "32  Epidemic curves" @@ -3079,7 +3079,7 @@ "href": "new_pages/age_pyramid.html#apyramid-package", "title": "33  Demographic pyramids and Likert-scales", "section": "33.2 apyramid package", - "text": "33.2 apyramid package\nThe package apyramid is a product of the R4Epis project. You can read more about this package here. It allows you to quickly make an age pyramid. For more nuanced situations, see the section below using ggplot(). You can read more about the apyramid package in its Help page by entering ?age_pyramid in your R console.\n\nLinelist data\nUsing the cleaned linelist dataset, we can create an age pyramid with one simple age_pyramid() command. In this command:\n\nThe data = argument is set as the linelist data frame\n\nThe age_group = argument (for y-axis) is set to the name of the categorical age column (in quotes)\n\nThe split_by = argument (for x-axis) is set to the gender column\n\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\")\n\n\n\n\n\n\n\n\nThe pyramid can be displayed with percent of all cases on the x-axis, instead of counts, by including proportional = TRUE.\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\",\n proportional = TRUE)\n\n\n\n\n\n\n\n\nWhen using agepyramid package, if the split_by column is binary (e.g. male/female, or yes/no), then the result will appear as a pyramid. However if there are more than two values in the split_by column (not including NA), the pyramid will appears as a faceted bar plot with grey bars in the “background” indicating the range of the un-faceted data for that age group. In this case, values of split_by = will appear as labels at top of each facet panel. For example, below is what occurs if the split_by = is assigned the column hospital.\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"hospital\") \n\n\n\n\n\n\n\n\n\nMissing values\nRows that have NA missing values in the split_by = or age_group = columns, if coded as NA, will not trigger the faceting shown above. By default these rows will not be shown. However you can specify that they appear, in an adjacent barplot and as a separate age group at the top, by specifying na.rm = FALSE.\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\",\n na.rm = FALSE) # show patients missing age or gender\n\n\n\n\n\n\n\n\n\n\nProportions, colors, & aesthetics\nBy default, the bars display counts (not %), a dashed mid-line for each group is shown, and the colors are green/purple. Each of these parameters can be adjusted, as shown below:\nYou can also add additional ggplot() commands to the plot using the standard ggplot() “+” syntax, such as aesthetic themes and label adjustments:\n\napyramid::age_pyramid(\n data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\",\n proportional = TRUE, # show percents, not counts\n show_midpoint = FALSE, # remove bar mid-point line\n #pal = c(\"orange\", \"purple\") # can specify alt. colors here (but not labels)\n )+ \n \n # additional ggplot commands\n theme_minimal()+ # simplfy background\n scale_fill_manual( # specify colors AND labels\n values = c(\"orange\", \"purple\"), \n labels = c(\"m\" = \"Male\", \"f\" = \"Female\"))+\n labs(y = \"Percent of all cases\", # note x and y labs are switched\n x = \"Age categories\", \n fill = \"Gender\", \n caption = \"My data source and caption here\",\n title = \"Title of my plot\",\n subtitle = \"Subtitle with \\n a second line...\")+\n theme(\n legend.position = \"bottom\", # legend to bottom\n axis.text = element_text(size = 10, face = \"bold\"), # fonts/sizes\n axis.title = element_text(size = 12, face = \"bold\"))\n\n\n\n\n\n\n\n\n\n\n\nAggregated data\nThe examples above assume your data are in a linelist format, with one row per observation. If your data are already aggregated into counts by age category, you can still use the apyramid package, as shown below.\nFor demonstration, we aggregate the linelist data into counts by age category and gender, into a “wide” format. This will simulate as if your data were in counts to begin with. Learn more about Grouping data and Pivoting data in their respective pages.\n\ndemo_agg <- linelist %>% \n count(age_cat5, gender, name = \"cases\") %>% \n pivot_wider(\n id_cols = age_cat5,\n names_from = gender,\n values_from = cases) %>% \n rename(`missing_gender` = `NA`)\n\n…which makes the dataset looks like this: with columns for age category, and male counts, female counts, and missing counts.\n\n\n\n\n\n\nTo set-up these data for the age pyramid, we will pivot the data to be “long” with the pivot_longer() function from dplyr. This is because ggplot() generally prefers “long” data, and apyramid is using ggplot().\n\n# pivot the aggregated data into long format\ndemo_agg_long <- demo_agg %>% \n pivot_longer(\n col = c(f, m, missing_gender), # cols to elongate\n names_to = \"gender\", # name for new col of categories\n values_to = \"counts\") %>% # name for new col of counts\n mutate(\n gender = na_if(gender, \"missing_gender\")) # convert \"missing_gender\" to NA\n\n\n\n\n\n\n\nThen use the split_by = and count = arguments of age_pyramid() to specify the respective columns in the data:\n\napyramid::age_pyramid(data = demo_agg_long,\n age_group = \"age_cat5\",# column name for age category\n split_by = \"gender\", # column name for gender\n count = \"counts\") # column name for case counts\n\n\n\n\n\n\n\n\nNote in the above, that the factor order of “m” and “f” is different (pyramid reversed). To adjust the order you must re-define gender in the aggregated data as a Factor and order the levels as desired. See the Factors page.", + "text": "33.2 apyramid package\nThe package apyramid is a product of the R4Epis project. You can read more about this package here. It allows you to quickly make an age pyramid. For more nuanced situations, see the section below using ggplot(). You can read more about the apyramid package in its Help page by entering ?age_pyramid in your R console.\n\nLinelist data\nUsing the cleaned linelist dataset, we can create an age pyramid with one simple age_pyramid() command. In this command:\n\nThe data = argument is set as the linelist data frame.\n\nThe age_group = argument (for y-axis) is set to the name of the categorical age column (in quotes).\n\nThe split_by = argument (for x-axis) is set to the gender column.\n\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\")\n\n\n\n\n\n\n\n\nThe pyramid can be displayed with percent of all cases on the x-axis, instead of counts, by including proportional = TRUE.\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\",\n proportional = TRUE)\n\n\n\n\n\n\n\n\nWhen using agepyramid package, if the split_by column is binary (e.g. male/female, or yes/no), then the result will appear as a pyramid. However if there are more than two values in the split_by column (not including NA), the pyramid will appears as a faceted bar plot with grey bars in the “background” indicating the range of the un-faceted data for that age group. In this case, values of split_by = will appear as labels at top of each facet panel. For example, below is what occurs if the split_by = is assigned the column hospital.\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"hospital\") \n\n\n\n\n\n\n\n\n\nMissing values\nRows that have NA missing values in the split_by = or age_group = columns, if coded as NA, will not trigger the faceting shown above. By default these rows will not be shown. However you can specify that they appear, in an adjacent barplot and as a separate age group at the top, by specifying na.rm = FALSE.\n\napyramid::age_pyramid(data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\",\n na.rm = FALSE) # show patients missing age or gender\n\n\n\n\n\n\n\n\n\n\nProportions, colors, & aesthetics\nBy default, the bars display counts (not %), a dashed mid-line for each group is shown, and the colors are green/purple. Each of these parameters can be adjusted, as shown below:\nYou can also add additional ggplot() commands to the plot using the standard ggplot() “+” syntax, such as aesthetic themes and label adjustments:\n\napyramid::age_pyramid(\n data = linelist,\n age_group = \"age_cat5\",\n split_by = \"gender\",\n proportional = TRUE, # show percents, not counts\n show_midpoint = FALSE, # remove bar mid-point line\n #pal = c(\"orange\", \"purple\") # can specify alt. colors here (but not labels)\n ) + \n \n # additional ggplot commands\n theme_minimal() + # simplfy background\n scale_fill_manual( # specify colors AND labels\n values = c(\"orange\", \"purple\"), \n labels = c(\"m\" = \"Male\", \"f\" = \"Female\")) +\n labs(y = \"Percent of all cases\", # note x and y labs are switched\n x = \"Age categories\", \n fill = \"Gender\", \n caption = \"My data source and caption here\",\n title = \"Title of my plot\",\n subtitle = \"Subtitle with \\n a second line...\") +\n theme(\n legend.position = \"bottom\", # legend to bottom\n axis.text = element_text(size = 10, face = \"bold\"), # fonts/sizes\n axis.title = element_text(size = 12, face = \"bold\"))\n\n\n\n\n\n\n\n\n\n\n\nAggregated data\nThe examples above assume your data are in a linelist format, with one row per observation. If your data are already aggregated into counts by age category, you can still use the apyramid package, as shown below.\nFor demonstration, we aggregate the linelist data into counts by age category and gender, into a “wide” format. This will simulate as if your data were in counts to begin with. Learn more about Grouping data and Pivoting data in their respective pages.\n\ndemo_agg <- linelist %>% \n count(age_cat5, gender, name = \"cases\") %>% \n pivot_wider(\n id_cols = age_cat5,\n names_from = gender,\n values_from = cases) %>% \n rename(`missing_gender` = `NA`)\n\nWhich makes the dataset looks like this: with columns for age category, and male counts, female counts, and missing counts.\n\n\n\n\n\n\nTo set-up these data for the age pyramid, we will pivot the data to be “long” with the pivot_longer() function from dplyr. This is because ggplot() generally prefers “long” data, and apyramid is using ggplot().\n\n# pivot the aggregated data into long format\ndemo_agg_long <- demo_agg %>% \n pivot_longer(\n col = c(f, m, missing_gender), # cols to elongate\n names_to = \"gender\", # name for new col of categories\n values_to = \"counts\") %>% # name for new col of counts\n mutate(\n gender = na_if(gender, \"missing_gender\")) # convert \"missing_gender\" to NA\n\n\n\n\n\n\n\nThen use the split_by = and count = arguments of age_pyramid() to specify the respective columns in the data:\n\napyramid::age_pyramid(data = demo_agg_long,\n age_group = \"age_cat5\",# column name for age category\n split_by = \"gender\", # column name for gender\n count = \"counts\") # column name for case counts\n\n\n\n\n\n\n\n\nNote in the above, that the factor order of “m” and “f” is different (pyramid reversed). To adjust the order you must re-define gender in the aggregated data as a Factor and order the levels as desired. See the Factors page.", "crumbs": [ "Data Visualization", "33  Demographic pyramids and Likert-scales" @@ -3090,7 +3090,7 @@ "href": "new_pages/age_pyramid.html#demo_pyr_gg", "title": "33  Demographic pyramids and Likert-scales", "section": "33.3 ggplot()", - "text": "33.3 ggplot()\nUsing ggplot() to build your age pyramid allows for more flexibility, but requires more effort and understanding of how ggplot() works. It is also easier to accidentally make mistakes.\nTo use ggplot() to make demographic pyramids, you create two bar plots (one for each gender), convert the values in one plot to negative, and finally flip the x and y axes to display the bar plots vertically, their bases meeting in the plot middle.\n\nPreparation\nThis approach uses the numeric age column, not the categorical column of age_cat5. So we will check to ensure the class of this column is indeed numeric.\n\nclass(linelist$age)\n\n[1] \"numeric\"\n\n\nYou could use the same logic below to build a pyramid from categorical data using geom_col() instead of geom_histogram().\n\n\n\nConstructing the plot\nFirst, understand that to make such a pyramid using ggplot() the approach is as follows:\n\nWithin the ggplot(), create two histograms using the numeric age column. Create one for each of the two grouping values (in this case genders male and female). To do this, the data for each histogram are specified within their respective geom_histogram() commands, with the respective filters applied to linelist.\nOne graph will have positive count values, while the other will have its counts converted to negative values - this creates the “pyramid” with the 0 value in the middle of the plot. The negative values are created using a special ggplot2 term ..count.. and multiplying by -1.\nThe command coord_flip() switches the X and Y axes, resulting in the graphs turning vertical and creating the pyramid.\nLastly, the counts-axis value labels must be altered so they appear as “positive” counts on both sides of the pyramid (despite the underlying values on one side being negative).\n\nA simple version of this, using geom_histogram(), is below:\n\n # begin ggplot\n ggplot(mapping = aes(x = age, fill = gender)) +\n \n # female histogram\n geom_histogram(data = linelist %>% filter(gender == \"f\"),\n breaks = seq(0,85,5),\n colour = \"white\") +\n \n # male histogram (values converted to negative)\n geom_histogram(data = linelist %>% filter(gender == \"m\"),\n breaks = seq(0,85,5),\n mapping = aes(y = ..count..*(-1)),\n colour = \"white\") +\n \n # flip the X and Y axes\n coord_flip() +\n \n # adjust counts-axis scale\n scale_y_continuous(limits = c(-600, 900),\n breaks = seq(-600,900,100),\n labels = abs(seq(-600, 900, 100)))\n\n\n\n\n\n\n\n\nDANGER: If the limits of your counts axis are set too low, and a counts bar exceeds them, the bar will disappear entirely or be artificially shortened! Watch for this if analyzing data which is routinely updated. Prevent it by having your count-axis limits auto-adjust to your data, as below.\nThere are many things you can change/add to this simple version, including:\n\nAuto adjust counts-axis scale to your data (avoid errors discussed in warning below)\n\nManually specify colors and legend labels\n\nConvert counts to percents\nTo convert counts to percents (of total), do this in your data prior to plotting. Below, we get the age-gender counts, then ungroup(), and then mutate() to create new percent columns. If you want percents by gender, skip the ungroup step.\n\n# create dataset with proportion of total\npyramid_data <- linelist %>%\n count(age_cat5,\n gender,\n name = \"counts\") %>% \n ungroup() %>% # ungroup so percents are not by group\n mutate(percent = round(100*(counts / sum(counts, na.rm=T)), digits = 1), \n percent = case_when(\n gender == \"f\" ~ percent,\n gender == \"m\" ~ -percent, # convert male to negative\n TRUE ~ NA_real_)) # NA val must by numeric as well\n\nImportantly, we save the max and min values so we know what the limits of the scale should be. These will be used in the ggplot() command below.\n\nmax_per <- max(pyramid_data$percent, na.rm=T)\nmin_per <- min(pyramid_data$percent, na.rm=T)\n\nmax_per\n\n[1] 10.9\n\nmin_per\n\n[1] -7.1\n\n\nFinally we make the ggplot() on the percent data. We specify scale_y_continuous() to extend the pre-defined lengths in each direction (positive and “negative”). We use floor() and ceiling() to round decimals the appropriate direction (down or up) for the side of the axis.\n\n# begin ggplot\n ggplot()+ # default x-axis is age in years;\n\n # case data graph\n geom_col(data = pyramid_data,\n mapping = aes(\n x = age_cat5,\n y = percent,\n fill = gender), \n colour = \"white\")+ # white around each bar\n \n # flip the X and Y axes to make pyramid vertical\n coord_flip()+\n \n\n # adjust the axes scales\n # scale_x_continuous(breaks = seq(0,100,5), labels = seq(0,100,5)) +\n scale_y_continuous(\n limits = c(min_per, max_per),\n breaks = seq(from = floor(min_per), # sequence of values, by 2s\n to = ceiling(max_per),\n by = 2),\n labels = paste0(abs(seq(from = floor(min_per), # sequence of absolute values, by 2s, with \"%\"\n to = ceiling(max_per),\n by = 2)),\n \"%\"))+ \n\n # designate colors and legend labels manually\n scale_fill_manual(\n values = c(\"f\" = \"orange\",\n \"m\" = \"darkgreen\"),\n labels = c(\"Female\", \"Male\")) +\n \n # label values (remember X and Y flipped now)\n labs(\n title = \"Age and gender of cases\",\n x = \"Age group\",\n y = \"Percent of total\",\n fill = NULL,\n caption = stringr::str_glue(\"Data are from linelist \\nn = {nrow(linelist)} (age or sex missing for {sum(is.na(linelist$gender) | is.na(linelist$age_years))} cases) \\nData as of: {format(Sys.Date(), '%d %b %Y')}\")) +\n \n # display themes\n theme(\n panel.grid.major = element_blank(),\n panel.grid.minor = element_blank(),\n panel.background = element_blank(),\n axis.line = element_line(colour = \"black\"),\n plot.title = element_text(hjust = 0.5), \n plot.caption = element_text(hjust=0, size=11, face = \"italic\")\n )\n\n\n\n\n\n\n\n\n\n\n\nCompare to baseline\nWith the flexibility of ggplot(), you can have a second layer of bars in the background that represent the “true” or “baseline” population pyramid. This can provide a nice visualization to compare the observed with the baseline.\nImport and view the population data (see Download handbook and data page):\n\n# import the population demographics data\npop <- rio::import(\"country_demographics.csv\")\n\n\n\n\n\n\n\nFirst some data management steps:\nHere we record the order of age categories that we want to appear. Due to some quirks the way the ggplot() is implemented, in this specific scenario it is easiest to store these as a character vector and use them later in the plotting function.\n\n# record correct age cat levels\nage_levels <- c(\"0-4\",\"5-9\", \"10-14\", \"15-19\", \"20-24\",\n \"25-29\",\"30-34\", \"35-39\", \"40-44\", \"45-49\",\n \"50-54\", \"55-59\", \"60-64\", \"65-69\", \"70-74\",\n \"75-79\", \"80-84\", \"85+\")\n\nCombine the population and case data through the dplyr function bind_rows():\n\nFirst, ensure they have the exact same column names, age categories values, and gender values\n\nMake them have the same data structure: columns of age category, gender, counts, and percent of total\n\nBind them together, one on-top of the other (bind_rows())\n\n\n# create/transform populaton data, with percent of total\n########################################################\npop_data <- pop %>% \n pivot_longer( # pivot gender columns longer\n cols = c(m, f),\n names_to = \"gender\",\n values_to = \"counts\") %>% \n \n mutate(\n percent = round(100*(counts / sum(counts, na.rm=T)),1), # % of total\n percent = case_when( \n gender == \"f\" ~ percent,\n gender == \"m\" ~ -percent, # if male, convert % to negative\n TRUE ~ NA_real_))\n\nReview the changed population dataset\n\n\n\n\n\n\nNow implement the same for the case linelist. Slightly different because it begins with case-rows, not counts.\n\n# create case data by age/gender, with percent of total\n#######################################################\ncase_data <- linelist %>%\n count(age_cat5, gender, name = \"counts\") %>% # counts by age-gender groups\n ungroup() %>% \n mutate(\n percent = round(100*(counts / sum(counts, na.rm=T)),1), # calculate % of total for age-gender groups\n percent = case_when( # convert % to negative if male\n gender == \"f\" ~ percent,\n gender == \"m\" ~ -percent,\n TRUE ~ NA_real_))\n\nReview the changed case dataset\n\n\n\n\n\n\nNow the two data frames are combined, one on top of the other (they have the same column names). We can “name” each of the data frame, and use the .id = argument to create a new column “data_source” that will indicate which data frame each row originated from. We can use this column to filter in the ggplot().\n\n# combine case and population data (same column names, age_cat values, and gender values)\npyramid_data <- bind_rows(\"cases\" = case_data, \"population\" = pop_data, .id = \"data_source\")\n\nStore the maximum and minimum percent values, used in the plotting function to define the extent of the plot (and not cut short any bars!)\n\n# Define extent of percent axis, used for plot limits\nmax_per <- max(pyramid_data$percent, na.rm=T)\nmin_per <- min(pyramid_data$percent, na.rm=T)\n\nNow the plot is made with ggplot():\n\nOne bar graph of population data (wider, more transparent bars)\nOne bar graph of case data (small, more solid bars)\n\n\n# begin ggplot\n##############\nggplot()+ # default x-axis is age in years;\n\n # population data graph\n geom_col(\n data = pyramid_data %>% filter(data_source == \"population\"),\n mapping = aes(\n x = age_cat5,\n y = percent,\n fill = gender),\n colour = \"black\", # black color around bars\n alpha = 0.2, # more transparent\n width = 1)+ # full width\n \n # case data graph\n geom_col(\n data = pyramid_data %>% filter(data_source == \"cases\"), \n mapping = aes(\n x = age_cat5, # age categories as original X axis\n y = percent, # % as original Y-axis\n fill = gender), # fill of bars by gender\n colour = \"black\", # black color around bars\n alpha = 1, # not transparent \n width = 0.3)+ # half width\n \n # flip the X and Y axes to make pyramid vertical\n coord_flip()+\n \n # manually ensure that age-axis is ordered correctly\n scale_x_discrete(limits = age_levels)+ # defined in chunk above\n \n # set percent-axis \n scale_y_continuous(\n limits = c(min_per, max_per), # min and max defined above\n breaks = seq(floor(min_per), ceiling(max_per), by = 2), # from min% to max% by 2 \n labels = paste0( # for the labels, paste together... \n abs(seq(floor(min_per), ceiling(max_per), by = 2)), \"%\"))+ \n\n # designate colors and legend labels manually\n scale_fill_manual(\n values = c(\"f\" = \"orange\", # assign colors to values in the data\n \"m\" = \"darkgreen\"),\n labels = c(\"f\" = \"Female\",\n \"m\"= \"Male\"), # change labels that appear in legend, note order\n ) +\n\n # plot labels, titles, caption \n labs(\n title = \"Case age and gender distribution,\\nas compared to baseline population\",\n subtitle = \"\",\n x = \"Age category\",\n y = \"Percent of total\",\n fill = NULL,\n caption = stringr::str_glue(\"Cases shown on top of country demographic baseline\\nCase data are from linelist, n = {nrow(linelist)}\\nAge or gender missing for {sum(is.na(linelist$gender) | is.na(linelist$age_years))} cases\\nCase data as of: {format(max(linelist$date_onset, na.rm=T), '%d %b %Y')}\")) +\n \n # optional aesthetic themes\n theme(\n legend.position = \"bottom\", # move legend to bottom\n panel.grid.major = element_blank(),\n panel.grid.minor = element_blank(),\n panel.background = element_blank(),\n axis.line = element_line(colour = \"black\"),\n plot.title = element_text(hjust = 0), \n plot.caption = element_text(hjust=0, size=11, face = \"italic\"))", + "text": "33.3 ggplot()\nUsing ggplot() to build your age pyramid allows for more flexibility, but requires more effort and understanding of how ggplot() works. It is also easier to accidentally make mistakes.\nTo use ggplot() to make demographic pyramids, you create two bar plots (one for each gender), convert the values in one plot to negative, and finally flip the x and y axes to display the bar plots vertically, their bases meeting in the plot middle.\n\nPreparation\nThis approach uses the numeric age column, not the categorical column of age_cat5. So we will check to ensure the class of this column is indeed numeric.\n\nclass(linelist$age)\n\n[1] \"numeric\"\n\n\nYou could use the same logic below to build a pyramid from categorical data using geom_col() instead of geom_histogram().\n\n\n\nConstructing the plot\nFirst, understand that to make such a pyramid using ggplot() the approach is as follows:\n\nWithin the ggplot(), create two histograms using the numeric age column. Create one for each of the two grouping values (in this case genders male and female). To do this, the data for each histogram are specified within their respective geom_histogram() commands, with the respective filters applied to linelist.\nOne graph will have positive count values, while the other will have its counts converted to negative values - this creates the “pyramid” with the 0 value in the middle of the plot. The negative values are created using a special ggplot2 term ..count.. and multiplying by -1.\nThe command coord_flip() switches the X and Y axes, resulting in the graphs turning vertical and creating the pyramid.\nLastly, the counts-axis value labels must be altered so they appear as “positive” counts on both sides of the pyramid (despite the underlying values on one side being negative).\n\nA simple version of this, using geom_histogram(), is below:\n\n # begin ggplot\n ggplot(mapping = aes(x = age, fill = gender)) +\n \n # female histogram\n geom_histogram(data = linelist %>% filter(gender == \"f\"),\n breaks = seq(0,85,5),\n colour = \"white\") +\n \n # male histogram (values converted to negative)\n geom_histogram(data = linelist %>% filter(gender == \"m\"),\n breaks = seq(0,85,5),\n mapping = aes(y = ..count..*(-1)),\n colour = \"white\") +\n \n # flip the X and Y axes\n coord_flip() +\n \n # adjust counts-axis scale\n scale_y_continuous(limits = c(-600, 900),\n breaks = seq(-600,900,100),\n labels = abs(seq(-600, 900, 100)))\n\n\n\n\n\n\n\n\nDANGER: If the limits of your counts axis are set too low, and a counts bar exceeds them, the bar will disappear entirely or be artificially shortened! Watch for this if analyzing data which is routinely updated. Prevent it by having your count-axis limits auto-adjust to your data, as below.\nThere are many things you can change/add to this simple version, including:\n\nAuto adjust counts-axis scale to your data (avoid errors discussed in warning below).\n\nManually specify colors and legend labels.\n\nConvert counts to percents\nTo convert counts to percents (of total), do this in your data prior to plotting. Below, we get the age-gender counts, then ungroup(), and then mutate() to create new percent columns. If you want percents by gender, skip the ungroup step.\n\n# create dataset with proportion of total\npyramid_data <- linelist %>%\n count(age_cat5,\n gender,\n name = \"counts\") %>% \n ungroup() %>% # ungroup so percents are not by group\n mutate(percent = round(100*(counts / sum(counts, na.rm=T)), digits = 1), \n percent = case_when(\n gender == \"f\" ~ percent,\n gender == \"m\" ~ -percent, # convert male to negative\n TRUE ~ NA_real_)) # NA val must by numeric as well\n\nImportantly, we save the max and min values so we know what the limits of the scale should be. These will be used in the ggplot() command below.\n\nmax_per <- max(pyramid_data$percent, na.rm=T)\nmin_per <- min(pyramid_data$percent, na.rm=T)\n\nmax_per\n\n[1] 10.9\n\nmin_per\n\n[1] -7.1\n\n\nFinally we make the ggplot() on the percent data. We specify scale_y_continuous() to extend the pre-defined lengths in each direction (positive and “negative”). We use floor() and ceiling() to round decimals the appropriate direction (down or up) for the side of the axis.\n\n# begin ggplot\n ggplot() + # default x-axis is age in years;\n\n # case data graph\n geom_col(data = pyramid_data,\n mapping = aes(\n x = age_cat5,\n y = percent,\n fill = gender), \n colour = \"white\") + # white around each bar\n \n # flip the X and Y axes to make pyramid vertical\n coord_flip() +\n \n\n # adjust the axes scales\n # scale_x_continuous(breaks = seq(0,100,5), labels = seq(0,100,5)) +\n scale_y_continuous(\n limits = c(min_per, max_per),\n breaks = seq(from = floor(min_per), # sequence of values, by 2s\n to = ceiling(max_per),\n by = 2),\n labels = paste0(abs(seq(from = floor(min_per), # sequence of absolute values, by 2s, with \"%\"\n to = ceiling(max_per),\n by = 2)),\n \"%\")) + \n\n # designate colors and legend labels manually\n scale_fill_manual(\n values = c(\"f\" = \"orange\",\n \"m\" = \"darkgreen\"),\n labels = c(\"Female\", \"Male\")) +\n \n # label values (remember X and Y flipped now)\n labs(\n title = \"Age and gender of cases\",\n x = \"Age group\",\n y = \"Percent of total\",\n fill = NULL,\n caption = stringr::str_glue(\"Data are from linelist \\nn = {nrow(linelist)} (age or sex missing for {sum(is.na(linelist$gender) | is.na(linelist$age_years))} cases) \\nData as of: {format(Sys.Date(), '%d %b %Y')}\")) +\n \n # display themes\n theme(\n panel.grid.major = element_blank(),\n panel.grid.minor = element_blank(),\n panel.background = element_blank(),\n axis.line = element_line(colour = \"black\"),\n plot.title = element_text(hjust = 0.5), \n plot.caption = element_text(hjust=0, size=11, face = \"italic\")\n )\n\n\n\n\n\n\n\n\n\n\n\nCompare to baseline\nWith the flexibility of ggplot(), you can have a second layer of bars in the background that represent the “true” or “baseline” population pyramid. This can provide a nice visualization to compare the observed with the baseline.\nImport and view the population data (see Download handbook and data page):\n\n# import the population demographics data\npop <- rio::import(\"country_demographics.csv\")\n\n\n\n\n\n\n\nFirst some data management steps:\nHere we record the order of age categories that we want to appear. Due to some quirks the way the ggplot() is implemented, in this specific scenario it is easiest to store these as a character vector and use them later in the plotting function.\n\n# record correct age cat levels\nage_levels <- c(\"0-4\",\"5-9\", \"10-14\", \"15-19\", \"20-24\",\n \"25-29\",\"30-34\", \"35-39\", \"40-44\", \"45-49\",\n \"50-54\", \"55-59\", \"60-64\", \"65-69\", \"70-74\",\n \"75-79\", \"80-84\", \"85+\")\n\nCombine the population and case data through the dplyr function bind_rows():\n\nFirst, ensure they have the exact same column names, age categories values, and gender values.\n\nMake them have the same data structure: columns of age category, gender, counts, and percent of total.\n\nBind them together, one on-top of the other (bind_rows()).\n\n\n# create/transform populaton data, with percent of total\n########################################################\npop_data <- pop %>% \n pivot_longer( # pivot gender columns longer\n cols = c(m, f),\n names_to = \"gender\",\n values_to = \"counts\") %>% \n \n mutate(\n percent = round(100*(counts / sum(counts, na.rm=T)),1), # % of total\n percent = case_when( \n gender == \"f\" ~ percent,\n gender == \"m\" ~ -percent, # if male, convert % to negative\n TRUE ~ NA_real_))\n\nReview the changed population dataset\n\n\n\n\n\n\nNow implement the same for the case linelist. Slightly different because it begins with case-rows, not counts.\n\n# create case data by age/gender, with percent of total\n#######################################################\ncase_data <- linelist %>%\n count(age_cat5, gender, name = \"counts\") %>% # counts by age-gender groups\n ungroup() %>% \n mutate(\n percent = round(100*(counts / sum(counts, na.rm=T)),1), # calculate % of total for age-gender groups\n percent = case_when( # convert % to negative if male\n gender == \"f\" ~ percent,\n gender == \"m\" ~ -percent,\n TRUE ~ NA_real_))\n\nReview the changed case dataset\n\n\n\n\n\n\nNow the two data frames are combined, one on top of the other (they have the same column names). We can “name” each of the data frame, and use the .id = argument to create a new column “data_source” that will indicate which data frame each row originated from. We can use this column to filter in the ggplot().\n\n# combine case and population data (same column names, age_cat values, and gender values)\npyramid_data <- bind_rows(\"cases\" = case_data, \"population\" = pop_data, .id = \"data_source\")\n\nStore the maximum and minimum percent values, used in the plotting function to define the extent of the plot (and not cut short any bars!)\n\n# Define extent of percent axis, used for plot limits\nmax_per <- max(pyramid_data$percent, na.rm = T)\nmin_per <- min(pyramid_data$percent, na.rm = T)\n\nNow the plot is made with ggplot():\n\nOne bar graph of population data (wider, more transparent bars).\nOne bar graph of case data (small, more solid bars).\n\n\n# begin ggplot\n##############\nggplot() + # default x-axis is age in years;\n\n # population data graph\n geom_col(\n data = pyramid_data %>% filter(data_source == \"population\"),\n mapping = aes(\n x = age_cat5,\n y = percent,\n fill = gender),\n colour = \"black\", # black color around bars\n alpha = 0.2, # more transparent\n width = 1) + # full width\n \n # case data graph\n geom_col(\n data = pyramid_data %>% filter(data_source == \"cases\"), \n mapping = aes(\n x = age_cat5, # age categories as original X axis\n y = percent, # % as original Y-axis\n fill = gender), # fill of bars by gender\n colour = \"black\", # black color around bars\n alpha = 1, # not transparent \n width = 0.3) + # half width\n \n # flip the X and Y axes to make pyramid vertical\n coord_flip() +\n \n # manually ensure that age-axis is ordered correctly\n scale_x_discrete(limits = age_levels) + # defined in chunk above\n \n # set percent-axis \n scale_y_continuous(\n limits = c(min_per, max_per), # min and max defined above\n breaks = seq(floor(min_per), ceiling(max_per), by = 2), # from min% to max% by 2 \n labels = paste0( # for the labels, paste together... \n abs(seq(floor(min_per), ceiling(max_per), by = 2)), \"%\")) + \n\n # designate colors and legend labels manually\n scale_fill_manual(\n values = c(\"f\" = \"orange\", # assign colors to values in the data\n \"m\" = \"darkgreen\"),\n labels = c(\"f\" = \"Female\",\n \"m\"= \"Male\"), # change labels that appear in legend, note order\n ) +\n\n # plot labels, titles, caption \n labs(\n title = \"Case age and gender distribution,\\nas compared to baseline population\",\n subtitle = \"\",\n x = \"Age category\",\n y = \"Percent of total\",\n fill = NULL,\n caption = stringr::str_glue(\"Cases shown on top of country demographic baseline\\nCase data are from linelist, n = {nrow(linelist)}\\nAge or gender missing for {sum(is.na(linelist$gender) | is.na(linelist$age_years))} cases\\nCase data as of: {format(max(linelist$date_onset, na.rm=T), '%d %b %Y')}\")) +\n \n # optional aesthetic themes\n theme(\n legend.position = \"bottom\", # move legend to bottom\n panel.grid.major = element_blank(),\n panel.grid.minor = element_blank(),\n panel.background = element_blank(),\n axis.line = element_line(colour = \"black\"),\n plot.title = element_text(hjust = 0), \n plot.caption = element_text(hjust=0, size=11, face = \"italic\"))", "crumbs": [ "Data Visualization", "33  Demographic pyramids and Likert-scales" @@ -3101,7 +3101,7 @@ "href": "new_pages/age_pyramid.html#likert-scale", "title": "33  Demographic pyramids and Likert-scales", "section": "33.4 Likert scale", - "text": "33.4 Likert scale\nThe techniques used to make a population pyramid with ggplot() can also be used to make plots of Likert-scale survey data.\nImport the data (see Download handbook and data page if desired).\n\n# import the likert survey response data\nlikert_data <- rio::import(\"likert_data.csv\")\n\nStart with data that looks like this, with a categorical classification of each respondent (status) and their answers to 8 questions on a 4-point Likert-type scale (“Very poor”, “Poor”, “Good”, “Very good”).\n\n\n\n\n\n\nFirst, some data management steps:\n\nPivot the data longer\n\nCreate new column direction depending on whether response was generally “positive” or “negative”\n\nSet the Factor level order for the status column and the Response column\n\nStore the max count value so limits of plot are appropriate\n\n\nmelted <- likert_data %>% \n pivot_longer(\n cols = Q1:Q8,\n names_to = \"Question\",\n values_to = \"Response\") %>% \n mutate(\n \n direction = case_when(\n Response %in% c(\"Poor\",\"Very Poor\") ~ \"Negative\",\n Response %in% c(\"Good\", \"Very Good\") ~ \"Positive\",\n TRUE ~ \"Unknown\"),\n \n status = fct_relevel(status, \"Junior\", \"Intermediate\", \"Senior\"),\n \n # must reverse 'Very Poor' and 'Poor' for ordering to work\n Response = fct_relevel(Response, \"Very Good\", \"Good\", \"Very Poor\", \"Poor\")) \n\n# get largest value for scale limits\nmelted_max <- melted %>% \n count(status, Question) %>% # get counts\n pull(n) %>% # column 'n'\n max(na.rm=T) # get max\n\nNow make the plot. As in the age pyramids above, we are creating two bar plots and inverting the values of one of them to negative.\nWe use geom_bar() because our data are one row per observation, not aggregated counts. We use the special ggplot2 term ..count.. in one of the bar plots to invert the values negative (*-1), and we set position = \"stack\" so the values stack on top of each other.\n\n# make plot\nggplot()+\n \n # bar graph of the \"negative\" responses \n geom_bar(\n data = melted %>% filter(direction == \"Negative\"),\n mapping = aes(\n x = status,\n y = ..count..*(-1), # counts inverted to negative\n fill = Response),\n color = \"black\",\n closed = \"left\",\n position = \"stack\")+\n \n # bar graph of the \"positive responses\n geom_bar(\n data = melted %>% filter(direction == \"Positive\"),\n mapping = aes(\n x = status,\n fill = Response),\n colour = \"black\",\n closed = \"left\",\n position = \"stack\")+\n \n # flip the X and Y axes\n coord_flip()+\n \n # Black vertical line at 0\n geom_hline(yintercept = 0, color = \"black\", size=1)+\n \n # convert labels to all positive numbers\n scale_y_continuous(\n \n # limits of the x-axis scale\n limits = c(-ceiling(melted_max/10)*11, # seq from neg to pos by 10, edges rounded outward to nearest 5\n ceiling(melted_max/10)*10), \n \n # values of the x-axis scale\n breaks = seq(from = -ceiling(melted_max/10)*10,\n to = ceiling(melted_max/10)*10,\n by = 10),\n \n # labels of the x-axis scale\n labels = abs(unique(c(seq(-ceiling(melted_max/10)*10, 0, 10),\n seq(0, ceiling(melted_max/10)*10, 10))))) +\n \n # color scales manually assigned \n scale_fill_manual(\n values = c(\"Very Good\" = \"green4\", # assigns colors\n \"Good\" = \"green3\",\n \"Poor\" = \"yellow\",\n \"Very Poor\" = \"red3\"),\n breaks = c(\"Very Good\", \"Good\", \"Poor\", \"Very Poor\"))+ # orders the legend\n \n \n \n # facet the entire plot so each question is a sub-plot\n facet_wrap( ~ Question, ncol = 3)+\n \n # labels, titles, caption\n labs(\n title = str_glue(\"Likert-style responses\\nn = {nrow(likert_data)}\"),\n x = \"Respondent status\",\n y = \"Number of responses\",\n fill = \"\")+\n\n # display adjustments \n theme_minimal()+\n theme(axis.text = element_text(size = 12),\n axis.title = element_text(size = 14, face = \"bold\"),\n strip.text = element_text(size = 14, face = \"bold\"), # facet sub-titles\n plot.title = element_text(size = 20, face = \"bold\"),\n panel.background = element_rect(fill = NA, color = \"black\")) # black box around each facet", + "text": "33.4 Likert scale\nThe techniques used to make a population pyramid with ggplot() can also be used to make plots of Likert-scale survey data.\nImport the data (see Download handbook and data page if desired).\n\n# import the likert survey response data\nlikert_data <- rio::import(\"likert_data.csv\")\n\nStart with data that looks like this, with a categorical classification of each respondent (status) and their answers to 8 questions on a 4-point Likert-type scale (“Very poor”, “Poor”, “Good”, “Very good”).\n\n\n\n\n\n\nFirst, some data management steps:\n\nPivot the data longer.\n\nCreate new column direction depending on whether response was generally “positive” or “negative”.\n\nSet the Factor level order for the status column and the Response column.\n\nStore the max count value so limits of plot are appropriate.\n\n\nmelted <- likert_data %>% \n pivot_longer(\n cols = Q1:Q8,\n names_to = \"Question\",\n values_to = \"Response\") %>% \n mutate(\n \n direction = case_when(\n Response %in% c(\"Poor\",\"Very Poor\") ~ \"Negative\",\n Response %in% c(\"Good\", \"Very Good\") ~ \"Positive\",\n TRUE ~ \"Unknown\"),\n \n status = fct_relevel(status, \"Junior\", \"Intermediate\", \"Senior\"),\n \n # must reverse 'Very Poor' and 'Poor' for ordering to work\n Response = fct_relevel(Response, \"Very Good\", \"Good\", \"Very Poor\", \"Poor\")) \n\n# get largest value for scale limits\nmelted_max <- melted %>% \n count(status, Question) %>% # get counts\n pull(n) %>% # column 'n'\n max(na.rm=T) # get max\n\nNow make the plot. As in the age pyramids above, we are creating two bar plots and inverting the values of one of them to negative.\nWe use geom_bar() because our data are one row per observation, not aggregated counts. We use the special ggplot2 term ..count.. in one of the bar plots to invert the values negative (*-1), and we set position = \"stack\" so the values stack on top of each other.\n\n# make plot\nggplot() +\n \n # bar graph of the \"negative\" responses \n geom_bar(\n data = melted %>% filter(direction == \"Negative\"),\n mapping = aes(\n x = status,\n y = ..count..*(-1), # counts inverted to negative\n fill = Response),\n color = \"black\",\n closed = \"left\",\n position = \"stack\") +\n \n # bar graph of the \"positive responses\n geom_bar(\n data = melted %>% filter(direction == \"Positive\"),\n mapping = aes(\n x = status,\n fill = Response),\n colour = \"black\",\n closed = \"left\",\n position = \"stack\") +\n \n # flip the X and Y axes\n coord_flip() +\n \n # Black vertical line at 0\n geom_hline(yintercept = 0, color = \"black\", size=1) +\n \n # convert labels to all positive numbers\n scale_y_continuous(\n \n # limits of the x-axis scale\n limits = c(-ceiling(melted_max/10)*11, # seq from neg to pos by 10, edges rounded outward to nearest 5\n ceiling(melted_max/10)*10), \n \n # values of the x-axis scale\n breaks = seq(from = -ceiling(melted_max/10)*10,\n to = ceiling(melted_max/10)*10,\n by = 10),\n \n # labels of the x-axis scale\n labels = abs(unique(c(seq(-ceiling(melted_max/10)*10, 0, 10),\n seq(0, ceiling(melted_max/10)*10, 10))))) +\n \n # color scales manually assigned \n scale_fill_manual(\n values = c(\"Very Good\" = \"green4\", # assigns colors\n \"Good\" = \"green3\",\n \"Poor\" = \"yellow\",\n \"Very Poor\" = \"red3\"),\n breaks = c(\"Very Good\", \"Good\", \"Poor\", \"Very Poor\")) + # orders the legend\n \n \n \n # facet the entire plot so each question is a sub-plot\n facet_wrap( ~ Question, ncol = 3) +\n \n # labels, titles, caption\n labs(\n title = str_glue(\"Likert-style responses\\nn = {nrow(likert_data)}\"),\n x = \"Respondent status\",\n y = \"Number of responses\",\n fill = \"\") +\n\n # display adjustments \n theme_minimal() +\n theme(axis.text = element_text(size = 12),\n axis.title = element_text(size = 14, face = \"bold\"),\n strip.text = element_text(size = 14, face = \"bold\"), # facet sub-titles\n plot.title = element_text(size = 20, face = \"bold\"),\n panel.background = element_rect(fill = NA, color = \"black\")) # black box around each facet", "crumbs": [ "Data Visualization", "33  Demographic pyramids and Likert-scales" @@ -3145,7 +3145,7 @@ "href": "new_pages/heatmaps.html#transmission-matrix", "title": "34  Heat plots", "section": "34.2 Transmission matrix", - "text": "34.2 Transmission matrix\nHeat tiles can be useful to visualize matrices. One example is to display “who-infected-whom” in an outbreak. This assumes that you have information on transmission events.\nNote that the [Contact tracing] page contains another example of making a heat tile contact matrix, using a different (perhaps more simple) dataset where the ages of cases and their sources are neatly aligned in the same row of the data frame. This same data is used to make a density map in the [ggplot tips] page. This example below begins from a case linelist and so involves considerable data manipulation prior to achieving a plotable data frame. So there are many scenarios to chose from…\nWe begin from the case linelist of a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\nThe first 50 rows of the linelist are shown below for demonstration:\n\nlinelist <- import(\"linelist_cleaned.rds\")\n\nIn this linelist:\n\nThere is one row per case, as identified by case_id\n\nThere is a later column infector that contains the case_id of the infector, who is also a case in the linelist\n\n\n\n\n\n\n\n\nData preparation\nObjective: We need to achieve a “long”-style data frame that contains one row per possible age-to-age transmission route, with a numeric column containing that row’s proportion of all observed transmission events in the linelist.\nThis will take several data manuipulation steps to achieve:\n\nMake cases data frame\nTo begin, we create a data frame of the cases, their ages, and their infectors - we call the data frame case_ages. The first 50 rows are displayed below.\n\ncase_ages <- linelist %>% \n select(case_id, infector, age_cat) %>% \n rename(\"case_age_cat\" = \"age_cat\")\n\n\n\n\n\n\n\n\n\nMake infectors data frame\nNext, we create a data frame of the infectors - at the moment it consists of a single column. These are the infector IDs from the linelist. Not every case has a known infector, so we remove missing values. The first 50 rows are displayed below.\n\ninfectors <- linelist %>% \n select(infector) %>% \n drop_na(infector)\n\n\n\n\n\n\n\nNext, we use joins to procure the ages of the infectors. This is not simple, because in the linelist, the infector’s ages are not listed as such. We achieve this result by joining the case linelist to the infectors. We begin with the infectors, and left_join() (add) the case linelist such that the infector id column left-side “baseline” data frame joins to the case_id column in the right-side linelist data frame.\nThus, the data from the infector’s case record in the linelist (including age) is added to the infector row. The 50 first rows are displayed below.\n\ninfector_ages <- infectors %>% # begin with infectors\n left_join( # add the linelist data to each infector \n linelist,\n by = c(\"infector\" = \"case_id\")) %>% # match infector to their information as a case\n select(infector, age_cat) %>% # keep only columns of interest\n rename(\"infector_age_cat\" = \"age_cat\") # rename for clarity\n\n\n\n\n\n\n\nThen, we combine the cases and their ages with the infectors and their ages. Each of these data frame has the column infector, so it is used for the join. The first rows are displayed below:\n\nages_complete <- case_ages %>% \n left_join(\n infector_ages,\n by = \"infector\") %>% # each has the column infector\n drop_na() # drop rows with any missing data\n\nWarning in left_join(., infector_ages, by = \"infector\"): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 1 of `x` matches multiple rows in `y`.\nℹ Row 6 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n\nBelow, a simple cross-tabulation of counts between the case and infector age groups. Labels added for clarity.\n\ntable(cases = ages_complete$case_age_cat,\n infectors = ages_complete$infector_age_cat)\n\n infectors\ncases 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+\n 0-4 105 156 105 114 143 117 13 0\n 5-9 102 132 110 102 117 96 12 5\n 10-14 104 109 91 79 120 80 12 4\n 15-19 85 105 82 39 75 69 7 5\n 20-29 101 127 109 80 143 107 22 4\n 30-49 72 97 56 54 98 61 4 5\n 50-69 5 6 15 9 7 5 2 0\n 70+ 1 0 2 0 0 0 0 0\n\n\nWe can convert this table to a data frame with data.frame() from base R, which also automatically converts it to “long” format, which is desired for the ggplot(). The first rows are shown below.\n\nlong_counts <- data.frame(table(\n cases = ages_complete$case_age_cat,\n infectors = ages_complete$infector_age_cat))\n\n\n\n\n\n\n\nNow we do the same, but apply prop.table() from base R to the table so instead of counts we get proportions of the total. The first 50 rows are shown below.\n\nlong_prop <- data.frame(prop.table(table(\n cases = ages_complete$case_age_cat,\n infectors = ages_complete$infector_age_cat)))\n\n\n\n\n\n\n\n\n\n\nCreate heat plot\nNow finally we can create the heat plot with ggplot2 package, using the geom_tile() function. See the ggplot tips page to learn more extensively about color/fill scales, especially the scale_fill_gradient() function.\n\nIn the aesthetics aes() of geom_tile() set the x and y as the case age and infector age\n\nAlso in aes() set the argument fill = to the Freq column - this is the value that will be converted to a tile color\n\nSet a scale color with scale_fill_gradient() - you can specify the high/low colors\n\nNote that scale_color_gradient() is different! In this case you want the fill\n\n\nBecause the color is made via “fill”, you can use the fill = argument in labs() to change the legend title\n\n\nggplot(data = long_prop)+ # use long data, with proportions as Freq\n geom_tile( # visualize it in tiles\n aes(\n x = cases, # x-axis is case age\n y = infectors, # y-axis is infector age\n fill = Freq))+ # color of the tile is the Freq column in the data\n scale_fill_gradient( # adjust the fill color of the tiles\n low = \"blue\",\n high = \"orange\")+\n labs( # labels\n x = \"Case age\",\n y = \"Infector age\",\n title = \"Who infected whom\",\n subtitle = \"Frequency matrix of transmission events\",\n fill = \"Proportion of all\\ntranmsission events\" # legend title\n )", + "text": "34.2 Transmission matrix\nHeat tiles can be useful to visualize matrices. One example is to display “who-infected-whom” in an outbreak. This assumes that you have information on transmission events.\nNote that the Contact tracing page contains another example of making a heat tile contact matrix, using a different (perhaps more simple) dataset where the ages of cases and their sources are neatly aligned in the same row of the data frame. This same data is used to make a density map in the ggplot tips page. This example below begins from a case linelist and so involves considerable data manipulation prior to achieving a plottable data frame. So there are many scenarios to chose from.\nWe begin from the case linelist of a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\nThe first 50 rows of the linelist are shown below for demonstration:\n\nlinelist <- import(\"linelist_cleaned.rds\")\n\nIn this linelist:\n\nThere is one row per case, as identified by case_id\nThere is a later column infector that contains the case_id of the infector, who is also a case in the linelist\n\n\n\n\n\n\n\n\nData preparation\nObjective: We need to achieve a “long”-style data frame that contains one row per possible age-to-age transmission route, with a numeric column containing that row’s proportion of all observed transmission events in the linelist.\nThis will take several data manuipulation steps to achieve:\n\nMake cases data frame\nTo begin, we create a data frame of the cases, their ages, and their infectors - we call the data frame case_ages. The first 50 rows are displayed below.\n\ncase_ages <- linelist %>% \n select(case_id, infector, age_cat) %>% \n rename(\"case_age_cat\" = \"age_cat\")\n\n\n\n\n\n\n\n\n\nMake infectors data frame\nNext, we create a data frame of the infectors - at the moment it consists of a single column. These are the infector IDs from the linelist. Not every case has a known infector, so we remove missing values. The first 50 rows are displayed below.\n\ninfectors <- linelist %>% \n select(infector) %>% \n drop_na(infector)\n\n\n\n\n\n\n\nNext, we use joins to procure the ages of the infectors. This is not simple, because in the linelist, the infector’s ages are not listed as such. We achieve this result by joining the case linelist to the infectors. We begin with the infectors, and left_join() (add) the case linelist such that the infector id column left-side “baseline” data frame joins to the case_id column in the right-side linelist data frame.\nThus, the data from the infector’s case record in the linelist (including age) is added to the infector row. The 50 first rows are displayed below.\n\ninfector_ages <- infectors %>% # begin with infectors\n left_join( # add the linelist data to each infector \n linelist,\n by = c(\"infector\" = \"case_id\")) %>% # match infector to their information as a case\n select(infector, age_cat) %>% # keep only columns of interest\n rename(\"infector_age_cat\" = \"age_cat\") # rename for clarity\n\n\n\n\n\n\n\nThen, we combine the cases and their ages with the infectors and their ages. Each of these data frame has the column infector, so it is used for the join. The first rows are displayed below:\n\nages_complete <- case_ages %>% \n left_join(\n infector_ages,\n by = \"infector\") %>% # each has the column infector\n drop_na() # drop rows with any missing data\n\nWarning in left_join(., infector_ages, by = \"infector\"): Detected an unexpected many-to-many relationship between `x` and `y`.\nℹ Row 1 of `x` matches multiple rows in `y`.\nℹ Row 6 of `y` matches multiple rows in `x`.\nℹ If a many-to-many relationship is expected, set `relationship =\n \"many-to-many\"` to silence this warning.\n\n\n\n\n\n\n\n\nBelow, a simple cross-tabulation of counts between the case and infector age groups. Labels added for clarity.\n\ntable(cases = ages_complete$case_age_cat,\n infectors = ages_complete$infector_age_cat)\n\n infectors\ncases 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+\n 0-4 105 156 105 114 143 117 13 0\n 5-9 102 132 110 102 117 96 12 5\n 10-14 104 109 91 79 120 80 12 4\n 15-19 85 105 82 39 75 69 7 5\n 20-29 101 127 109 80 143 107 22 4\n 30-49 72 97 56 54 98 61 4 5\n 50-69 5 6 15 9 7 5 2 0\n 70+ 1 0 2 0 0 0 0 0\n\n\nWe can convert this table to a data frame with data.frame() from base R, which also automatically converts it to “long” format, which is desired for the ggplot(). The first rows are shown below.\n\nlong_counts <- data.frame(table(\n cases = ages_complete$case_age_cat,\n infectors = ages_complete$infector_age_cat))\n\n\n\n\n\n\n\nNow we do the same, but apply prop.table() from base R to the table so instead of counts we get proportions of the total. The first 50 rows are shown below.\n\nlong_prop <- data.frame(prop.table(table(\n cases = ages_complete$case_age_cat,\n infectors = ages_complete$infector_age_cat)))\n\n\n\n\n\n\n\n\n\n\nCreate heat plot\nNow finally we can create the heat plot with ggplot2 package, using the geom_tile() function. See the ggplot tips page to learn more extensively about color/fill scales, especially the scale_fill_gradient() function.\n\nIn the aesthetics aes() of geom_tile() set the x and y as the case age and infector age.\nAlso in aes() set the argument fill = to the Freq column - this is the value that will be converted to a tile color.\n\nSet a scale color with scale_fill_gradient() - you can specify the high/low colors.\n\nNote that scale_color_gradient() is different! In this case you want the fill.\n\n\nBecause the color is made via “fill”, you can use the fill = argument in labs() to change the legend title.\n\n\nggplot(data = long_prop) + # use long data, with proportions as Freq\n geom_tile( # visualize it in tiles\n aes(\n x = cases, # x-axis is case age\n y = infectors, # y-axis is infector age\n fill = Freq)) + # color of the tile is the Freq column in the data\n scale_fill_gradient( # adjust the fill color of the tiles\n low = \"blue\",\n high = \"orange\") +\n labs( # labels\n x = \"Case age\",\n y = \"Infector age\",\n title = \"Who infected whom\",\n subtitle = \"Frequency matrix of transmission events\",\n fill = \"Proportion of all\\ntranmsission events\" # legend title\n )", "crumbs": [ "Data Visualization", "34  Heat plots" @@ -3156,7 +3156,7 @@ "href": "new_pages/heatmaps.html#reporting-metrics-over-time", "title": "34  Heat plots", "section": "34.3 Reporting metrics over time", - "text": "34.3 Reporting metrics over time\nOften in public health, one objective is to assess trends over time for many entities (facilities, jurisdictions, etc.). One way to visualize such trends over time is a heat plot where the x-axis is time and on the y-axis are the many entities.\n\nData preparation\nWe begin by importing a dataset of daily malaria reports from many facilities. The reports contain a date, province, district, and malaria counts. See the page on Download handbook and data for information on how to download these data. Below are the first 30 rows:\n\nfacility_count_data <- import(\"malaria_facility_count_data.rds\")\n\n\n\n\n\n\n\n\nAggregate and summarize\nThe objective in this example is to transform the daily facility total malaria case counts (seen in previous tab) into weekly summary statistics of facility reporting performance - in this case the proportion of days per week that the facility reported any data. For this example we will show data only for Spring District.\nTo achieve this we will do the following data management steps:\n\nFilter the data as appropriate (by place, date)\n\nCreate a week column using floor_date() from package lubridate\n\nThis function returns the start-date of a given date’s week, using a specified start date of each week (e.g. “Mondays”)\n\n\nThe data are grouped by columns “location” and “week” to create analysis units of “facility-week”\n\nThe function summarise() creates new columns to reflecting summary statistics per facility-week group:\n\nNumber of days per week (7 - a static value)\n\nNumber of reports received from the facility-week (could be more than 7!)\n\nSum of malaria cases reported by the facility-week (just for interest)\n\nNumber of unique days in the facility-week for which there is data reported\n\nPercent of the 7 days per facility-week for which data was reported\n\n\nThe data frame is joined with right_join() to a comprehensive list of all possible facility-week combinations, to make the dataset complete. The matrix of all possible combinations is created by applying expand() to those two columns of the data frame as it is at that moment in the pipe chain (represented by .). Because a right_join() is used, all rows in the expand() data frame are kept, and added to agg_weeks if necessary. These new rows appear with NA (missing) summarized values.\n\nBelow we demonstrate step-by-step:\n\n# Create weekly summary dataset\nagg_weeks <- facility_count_data %>% \n \n # filter the data as appropriate\n filter(\n District == \"Spring\",\n data_date < as.Date(\"2020-08-01\")) \n\nNow the dataset has nrow(agg_weeks) rows, when it previously had nrow(facility_count_data).\nNext we create a week column reflecting the start date of the week for each record. This is achieved with the lubridate package and the function floor_date(), which is set to “week” and for the weeks to begin on Mondays (day 1 of the week - Sundays would be 7). The top rows are shown below.\n\nagg_weeks <- agg_weeks %>% \n # Create week column from data_date\n mutate(\n week = lubridate::floor_date( # create new column of weeks\n data_date, # date column\n unit = \"week\", # give start of the week\n week_start = 1)) # weeks to start on Mondays \n\nThe new week column can be seen on the far right of the data frame\n\n\n\n\n\n\nNow we group the data into facility-weeks and summarise them to produce statistics per facility-week. See the page on Descriptive tables for tips. The grouping itself doesn’t change the data frame, but it impacts how the subsequent summary statistics are calculated.\nThe top rows are shown below. Note how the columns have completely changed to reflect the desired summary statistics. Each row reflects one facility-week.\n\nagg_weeks <- agg_weeks %>% \n\n # Group into facility-weeks\n group_by(location_name, week) %>%\n \n # Create summary statistics columns on the grouped data\n summarize(\n n_days = 7, # 7 days per week \n n_reports = dplyr::n(), # number of reports received per week (could be >7)\n malaria_tot = sum(malaria_tot, na.rm = T), # total malaria cases reported\n n_days_reported = length(unique(data_date)), # number of unique days reporting per week\n p_days_reported = round(100*(n_days_reported / n_days))) %>% # percent of days reporting\n\n ungroup(location_name, week) # ungroup so expand() works in next step\n\n\n\n\n\n\n\nFinally, we run the command below to ensure that ALL possible facility-weeks are present in the data, even if they were missing before.\nWe are using a right_join() on itself (the dataset is represented by “.”) but having been expanded to include all possible combinations of the columns week and location_name. See documentation on the expand() function in the page on Pivoting. Before running this code the dataset contains nrow(agg_weeks) rows.\n\n# Create data frame of every possible facility-week\nexpanded_weeks <- agg_weeks %>% \n tidyr::expand(location_name, week) # expand data frame to include all possible facility-week combinations\n\nHere is expanded_weeks, with 180 rows:\n\n\n\n\n\n\nBefore running this code, agg_weeks contains 107 rows.\n\n# Use a right-join with the expanded facility-week list to fill-in the missing gaps in the data\nagg_weeks <- agg_weeks %>% \n right_join(expanded_weeks) %>% # Ensure every possible facility-week combination appears in the data\n mutate(p_days_reported = replace_na(p_days_reported, 0)) # convert missing values to 0 \n\nJoining with `by = join_by(location_name, week)`\n\n\nAfter running this code, agg_weeks contains nrow(agg_weeks) rows.\n\n\n\n\nCreate heat plot\nThe ggplot() is made using geom_tile() from the ggplot2 package:\n\nWeeks on the x-axis is transformed to dates, allowing use of scale_x_date()\n\nlocation_name on the y-axis will show all facility names\n\nThe fill is p_days_reported, the performance for that facility-week (numeric)\n\nscale_fill_gradient() is used on the numeric fill, specifying colors for high, low, and NA\n\nscale_x_date() is used on the x-axis specifying labels every 2 weeks and their format\n\nDisplay themes and labels can be adjusted as necessary\n\n\n\n\nBasic\nA basic heat plot is produced below, using the default colors, scales, etc. As explained above, within the aes() for geom_tile() you must provide an x-axis column, y-axis column, and a column for the the fill =. The fill is the numeric value that presents as tile color.\n\nggplot(data = agg_weeks)+\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported))\n\n\n\n\n\n\n\n\n\n\nCleaned plot\nWe can make this plot look better by adding additional ggplot2 functions, as shown below. See the page on ggplot tips for details.\n\nggplot(data = agg_weeks)+ \n \n # show data as tiles\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported), \n color = \"white\")+ # white gridlines\n \n scale_fill_gradient(\n low = \"orange\",\n high = \"darkgreen\",\n na.value = \"grey80\")+\n \n # date axis\n scale_x_date(\n expand = c(0,0), # remove extra space on sides\n date_breaks = \"2 weeks\", # labels every 2 weeks\n date_labels = \"%d\\n%b\")+ # format is day over month (\\n in newline)\n \n # aesthetic themes\n theme_minimal()+ # simplify background\n \n theme(\n legend.title = element_text(size=12, face=\"bold\"),\n legend.text = element_text(size=10, face=\"bold\"),\n legend.key.height = grid::unit(1,\"cm\"), # height of legend key\n legend.key.width = grid::unit(0.6,\"cm\"), # width of legend key\n \n axis.text.x = element_text(size=12), # axis text size\n axis.text.y = element_text(vjust=0.2), # axis text alignment\n axis.ticks = element_line(size=0.4), \n axis.title = element_text(size=12, face=\"bold\"), # axis title size and bold\n \n plot.title = element_text(hjust=0,size=14,face=\"bold\"), # title right-aligned, large, bold\n plot.caption = element_text(hjust = 0, face = \"italic\") # caption right-aligned and italic\n )+\n \n # plot labels\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\", # legend title, because legend shows fill\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, May-July 2020\",\n caption = \"7-day weeks beginning on Mondays.\")\n\n\n\n\n\n\n\n\n\n\n\nOrdered y-axis\nCurrently, the facilities are ordered “alpha-numerically” from the bottom to the top. If you want to adjust the order the y-axis facilities, convert them to class factor and provide the order. See the page on Factors for tips.\nSince there are many facilities and we don’t want to write them all out, we will try another approach - ordering the facilities in a data frame and using the resulting column of names as the factor level order. Below, the column location_name is converted to a factor, and the order of its levels is set based on the total number of reporting days filed by the facility across the whole time-span.\nTo do this, we create a data frame which represents the total number of reports per facility, arranged in ascending order. We can use this vector to order the factor levels in the plot.\n\nfacility_order <- agg_weeks %>% \n group_by(location_name) %>% \n summarize(tot_reports = sum(n_days_reported, na.rm=T)) %>% \n arrange(tot_reports) # ascending order\n\nSee the data frame below:\n\n\n\n\n\n\nNow use a column from the above data frame (facility_order$location_name) to be the order of the factor levels of location_name in the data frame agg_weeks:\n\n# load package \npacman::p_load(forcats)\n\n# create factor and define levels manually\nagg_weeks <- agg_weeks %>% \n mutate(location_name = fct_relevel(\n location_name, facility_order$location_name)\n )\n\nAnd now the data are re-plotted, with location_name being an ordered factor:\n\nggplot(data = agg_weeks)+ \n \n # show data as tiles\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported), \n color = \"white\")+ # white gridlines\n \n scale_fill_gradient(\n low = \"orange\",\n high = \"darkgreen\",\n na.value = \"grey80\")+\n \n # date axis\n scale_x_date(\n expand = c(0,0), # remove extra space on sides\n date_breaks = \"2 weeks\", # labels every 2 weeks\n date_labels = \"%d\\n%b\")+ # format is day over month (\\n in newline)\n \n # aesthetic themes\n theme_minimal()+ # simplify background\n \n theme(\n legend.title = element_text(size=12, face=\"bold\"),\n legend.text = element_text(size=10, face=\"bold\"),\n legend.key.height = grid::unit(1,\"cm\"), # height of legend key\n legend.key.width = grid::unit(0.6,\"cm\"), # width of legend key\n \n axis.text.x = element_text(size=12), # axis text size\n axis.text.y = element_text(vjust=0.2), # axis text alignment\n axis.ticks = element_line(size=0.4), \n axis.title = element_text(size=12, face=\"bold\"), # axis title size and bold\n \n plot.title = element_text(hjust=0,size=14,face=\"bold\"), # title right-aligned, large, bold\n plot.caption = element_text(hjust = 0, face = \"italic\") # caption right-aligned and italic\n )+\n \n # plot labels\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\", # legend title, because legend shows fill\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, May-July 2020\",\n caption = \"7-day weeks beginning on Mondays.\")\n\n\n\n\n\n\n\n\n\n\n\nDisplay values\nYou can add a geom_text() layer on top of the tiles, to display the actual numbers of each tile. Be aware this may not look pretty if you have many small tiles!\nThe following code has been added: geom_text(aes(label = p_days_reported)). This adds text onto every tile. The text displayed is the value assigned to the argument label =, which in this case has been set to the same numeric column p_days_reported that is also used to create the color gradient.\n\nggplot(data = agg_weeks)+ \n \n # show data as tiles\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported), \n color = \"white\")+ # white gridlines\n \n # text\n geom_text(\n aes(\n x = week,\n y = location_name,\n label = p_days_reported))+ # add text on top of tile\n \n # fill scale\n scale_fill_gradient(\n low = \"orange\",\n high = \"darkgreen\",\n na.value = \"grey80\")+\n \n # date axis\n scale_x_date(\n expand = c(0,0), # remove extra space on sides\n date_breaks = \"2 weeks\", # labels every 2 weeks\n date_labels = \"%d\\n%b\")+ # format is day over month (\\n in newline)\n \n # aesthetic themes\n theme_minimal()+ # simplify background\n \n theme(\n legend.title = element_text(size=12, face=\"bold\"),\n legend.text = element_text(size=10, face=\"bold\"),\n legend.key.height = grid::unit(1,\"cm\"), # height of legend key\n legend.key.width = grid::unit(0.6,\"cm\"), # width of legend key\n \n axis.text.x = element_text(size=12), # axis text size\n axis.text.y = element_text(vjust=0.2), # axis text alignment\n axis.ticks = element_line(size=0.4), \n axis.title = element_text(size=12, face=\"bold\"), # axis title size and bold\n \n plot.title = element_text(hjust=0,size=14,face=\"bold\"), # title right-aligned, large, bold\n plot.caption = element_text(hjust = 0, face = \"italic\") # caption right-aligned and italic\n )+\n \n # plot labels\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\", # legend title, because legend shows fill\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, May-July 2020\",\n caption = \"7-day weeks beginning on Mondays.\")", + "text": "34.3 Reporting metrics over time\nOften in public health, one objective is to assess trends over time for many entities (facilities, jurisdictions, etc.). One way to visualize such trends over time is a heat plot where the x-axis is time and on the y-axis are the many entities.\n\nData preparation\nWe begin by importing a dataset of daily malaria reports from many facilities. The reports contain a date, province, district, and malaria counts. See the page on Download handbook and data for information on how to download these data. Below are the first 30 rows:\n\nfacility_count_data <- import(\"malaria_facility_count_data.rds\")\n\n\n\n\n\n\n\n\nAggregate and summarize\nThe objective in this example is to transform the daily facility total malaria case counts (seen in previous tab) into weekly summary statistics of facility reporting performance - in this case the proportion of days per week that the facility reported any data. For this example we will show data only for Spring District.\nTo achieve this we will do the following data management steps:\n\nFilter the data as appropriate (by place, date).\n\nCreate a week column using floor_date() from package lubridate.\n\nThis function returns the start-date of a given date’s week, using a specified start date of each week (e.g. “Mondays”).\n\n\nThe data are grouped by columns “location” and “week” to create analysis units of “facility-week”.\n\nThe function summarise() creates new columns to reflecting summary statistics per facility-week group:\n\nNumber of days per week (7 - a static value)\nNumber of reports received from the facility-week (could be more than 7!)\nSum of malaria cases reported by the facility-week (just for interest)\nNumber of unique days in the facility-week for which there is data reported\nPercent of the 7 days per facility-week for which data was reported\n\nThe data frame is joined with right_join() to a comprehensive list of all possible facility-week combinations, to make the dataset complete. The matrix of all possible combinations is created by applying expand() to those two columns of the data frame as it is at that moment in the pipe chain (represented by .). Because a right_join() is used, all rows in the expand() data frame are kept, and added to agg_weeks if necessary. These new rows appear with NA (missing) summarized values.\n\nBelow we demonstrate step-by-step:\n\n# Create weekly summary dataset\nagg_weeks <- facility_count_data %>% \n # filter the data as appropriate\n filter(\n District == \"Spring\",\n data_date < as.Date(\"2020-08-01\")) \n\nNow the dataset has nrow(agg_weeks) rows, when it previously had nrow(facility_count_data).\nNext we create a week column reflecting the start date of the week for each record. This is achieved with the lubridate package and the function floor_date(), which is set to “week” and for the weeks to begin on Mondays (day 1 of the week - Sundays would be 7). The top rows are shown below.\n\nagg_weeks <- agg_weeks %>% \n # Create week column from data_date\n mutate(\n week = lubridate::floor_date( # create new column of weeks\n data_date, # date column\n unit = \"week\", # give start of the week\n week_start = 1)) # weeks to start on Mondays \n\nThe new week column can be seen on the far right of the data frame\n\n\n\n\n\n\nNow we group the data into facility-weeks and summarise them to produce statistics per facility-week. See the page on Descriptive tables for tips. The grouping itself doesn’t change the data frame, but it impacts how the subsequent summary statistics are calculated.\nThe top rows are shown below. Note how the columns have completely changed to reflect the desired summary statistics. Each row reflects one facility-week.\n\nagg_weeks <- agg_weeks %>% \n\n # Group into facility-weeks\n group_by(location_name, week) %>%\n \n # Create summary statistics columns on the grouped data\n summarize(\n n_days = 7, # 7 days per week \n n_reports = dplyr::n(), # number of reports received per week (could be >7)\n malaria_tot = sum(malaria_tot, na.rm = T), # total malaria cases reported\n n_days_reported = length(unique(data_date)), # number of unique days reporting per week\n p_days_reported = round(100*(n_days_reported / n_days))) %>% # percent of days reporting\n\n ungroup(location_name, week) # ungroup so expand() works in next step\n\n\n\n\n\n\n\nFinally, we run the command below to ensure that ALL possible facility-weeks are present in the data, even if they were missing before.\nWe are using a right_join() on itself (the dataset is represented by “.”) but having been expanded to include all possible combinations of the columns week and location_name. See documentation on the expand() function in the page on Pivoting. Before running this code the dataset contains nrow(agg_weeks) rows.\n\n# Create data frame of every possible facility-week\nexpanded_weeks <- agg_weeks %>% \n tidyr::expand(location_name, week) # expand data frame to include all possible facility-week combinations\n\nHere is expanded_weeks, with 180 rows:\n\n\n\n\n\n\nBefore running this code, agg_weeks contains 107 rows.\n\n# Use a right-join with the expanded facility-week list to fill-in the missing gaps in the data\nagg_weeks <- agg_weeks %>% \n right_join(expanded_weeks) %>% # Ensure every possible facility-week combination appears in the data\n mutate(p_days_reported = replace_na(p_days_reported, 0)) # convert missing values to 0 \n\nJoining with `by = join_by(location_name, week)`\n\n\nAfter running this code, agg_weeks contains nrow(agg_weeks) rows.\n\n\n\n\nCreate heat plot\nThe ggplot() is made using geom_tile() from the ggplot2 package:\n\nWeeks on the x-axis is transformed to dates, allowing use of scale_x_date()\nlocation_name on the y-axis will show all facility names\nThe fill is p_days_reported, the performance for that facility-week (numeric)\nscale_fill_gradient() is used on the numeric fill, specifying colors for high, low, and NA\nscale_x_date() is used on the x-axis specifying labels every 2 weeks and their format\nDisplay themes and labels can be adjusted as necessary\n\n\n\n\nBasic\nA basic heat plot is produced below, using the default colors, scales, etc. As explained above, within the aes() for geom_tile() you must provide an x-axis column, y-axis column, and a column for the the fill =. The fill is the numeric value that presents as tile color.\n\nggplot(data = agg_weeks) +\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported))\n\n\n\n\n\n\n\n\n\n\nCleaned plot\nWe can make this plot look better by adding additional ggplot2 functions, as shown below. See the page on ggplot tips for details.\n\nggplot(data = agg_weeks) + \n \n # show data as tiles\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported), \n color = \"white\") + # white gridlines\n \n scale_fill_gradient(\n low = \"orange\",\n high = \"darkgreen\",\n na.value = \"grey80\") +\n \n # date axis\n scale_x_date(\n expand = c(0,0), # remove extra space on sides\n date_breaks = \"2 weeks\", # labels every 2 weeks\n date_labels = \"%d\\n%b\") + # format is day over month (\\n in newline)\n \n # aesthetic themes\n theme_minimal() + # simplify background\n \n theme(\n legend.title = element_text(size=12, face=\"bold\"),\n legend.text = element_text(size=10, face=\"bold\"),\n legend.key.height = grid::unit(1,\"cm\"), # height of legend key\n legend.key.width = grid::unit(0.6,\"cm\"), # width of legend key\n \n axis.text.x = element_text(size=12), # axis text size\n axis.text.y = element_text(vjust=0.2), # axis text alignment\n axis.ticks = element_line(size=0.4), \n axis.title = element_text(size=12, face=\"bold\"), # axis title size and bold\n \n plot.title = element_text(hjust=0,size=14,face=\"bold\"), # title right-aligned, large, bold\n plot.caption = element_text(hjust = 0, face = \"italic\") # caption right-aligned and italic\n ) +\n \n # plot labels\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\", # legend title, because legend shows fill\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, May-July 2020\",\n caption = \"7-day weeks beginning on Mondays.\")\n\n\n\n\n\n\n\n\n\n\n\nOrdered y-axis\nCurrently, the facilities are ordered “alpha-numerically” from the bottom to the top. If you want to adjust the order the y-axis facilities, convert them to class factor and provide the order. See the page on Factors for tips.\nSince there are many facilities and we don’t want to write them all out, we will try another approach - ordering the facilities in a data frame and using the resulting column of names as the factor level order. Below, the column location_name is converted to a factor, and the order of its levels is set based on the total number of reporting days filed by the facility across the whole time-span.\nTo do this, we create a data frame which represents the total number of reports per facility, arranged in ascending order. We can use this vector to order the factor levels in the plot.\n\nfacility_order <- agg_weeks %>% \n group_by(location_name) %>% \n summarize(tot_reports = sum(n_days_reported, na.rm=T)) %>% \n arrange(tot_reports) # ascending order\n\nSee the data frame below:\n\n\n\n\n\n\nNow use a column from the above data frame (facility_order$location_name) to be the order of the factor levels of location_name in the data frame agg_weeks:\n\n# load package \npacman::p_load(forcats)\n\n# create factor and define levels manually\nnumerical_order <- gsub(\"Facility \", \"\", facility_order$location_name) %>% \n as.numeric() %>%\n sort() \n\nfacilities_in_order <- str_c(\"Facility \", numerical_order, sep = \"\")\n\nagg_weeks <- agg_weeks %>% \n mutate(location_name = fct_relevel(\n location_name, facilities_in_order)\n )\n\nAnd now the data are re-plotted, with location_name being an ordered factor:\n\nggplot(data = agg_weeks) + \n \n # show data as tiles\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported), \n color = \"white\") + # white gridlines\n \n scale_fill_gradient(\n low = \"orange\",\n high = \"darkgreen\",\n na.value = \"grey80\") +\n \n # date axis\n scale_x_date(\n expand = c(0,0), # remove extra space on sides\n date_breaks = \"2 weeks\", # labels every 2 weeks\n date_labels = \"%d\\n%b\") + # format is day over month (\\n in newline)\n \n # aesthetic themes\n theme_minimal() + # simplify background\n \n theme(\n legend.title = element_text(size = 12, face = \"bold\"),\n legend.text = element_text(size = 10, face = \"bold\"),\n legend.key.height = grid::unit(1,\"cm\"), # height of legend key\n legend.key.width = grid::unit(0.6,\"cm\"), # width of legend key\n \n axis.text.x = element_text(size = 12), # axis text size\n axis.text.y = element_text(vjust = 0.2), # axis text alignment\n axis.ticks = element_line(size = 0.4), \n axis.title = element_text(size = 12, face = \"bold\"), # axis title size and bold\n \n plot.title = element_text(hjust = 0, size = 14, face = \"bold\"), # title right-aligned, large, bold\n plot.caption = element_text(hjust = 0, face = \"italic\") # caption right-aligned and italic\n ) +\n \n # plot labels\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\", # legend title, because legend shows fill\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, May-July 2020\",\n caption = \"7-day weeks beginning on Mondays.\")\n\n\n\n\n\n\n\n\n\n\n\nDisplay values\nYou can add a geom_text() layer on top of the tiles, to display the actual numbers of each tile. Be aware this may not look pretty if you have many small tiles!\nThe following code has been added: geom_text(aes(label = p_days_reported)). This adds text onto every tile. The text displayed is the value assigned to the argument label =, which in this case has been set to the same numeric column p_days_reported that is also used to create the color gradient.\n\nggplot(data = agg_weeks) + \n \n # show data as tiles\n geom_tile(\n aes(x = week,\n y = location_name,\n fill = p_days_reported), \n color = \"white\") + # white gridlines\n \n # text\n geom_text(\n aes(\n x = week,\n y = location_name,\n label = p_days_reported)) + # add text on top of tile\n \n # fill scale\n scale_fill_gradient(\n low = \"orange\",\n high = \"darkgreen\",\n na.value = \"grey80\") +\n \n # date axis\n scale_x_date(\n expand = c(0,0), # remove extra space on sides\n date_breaks = \"2 weeks\", # labels every 2 weeks\n date_labels = \"%d\\n%b\") + # format is day over month (\\n in newline)\n \n # aesthetic themes\n theme_minimal() + # simplify background\n \n theme(\n legend.title = element_text(size = 12, face = \"bold\"),\n legend.text = element_text(size = 10, face = \"bold\"),\n legend.key.height = grid::unit(1,\"cm\"), # height of legend key\n legend.key.width = grid::unit(0.6,\"cm\"), # width of legend key\n \n axis.text.x = element_text(size = 12), # axis text size\n axis.text.y = element_text(vjust = 0.2), # axis text alignment\n axis.ticks = element_line(size = 0.4), \n axis.title = element_text(size = 12, face = \"bold\"), # axis title size and bold\n \n plot.title = element_text(hjust = 0,size = 14,face = \"bold\"), # title right-aligned, large, bold\n plot.caption = element_text(hjust = 0, face = \"italic\") # caption right-aligned and italic\n ) +\n \n # plot labels\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\", # legend title, because legend shows fill\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, May-July 2020\",\n caption = \"7-day weeks beginning on Mondays.\")", "crumbs": [ "Data Visualization", "34  Heat plots" @@ -3189,7 +3189,7 @@ "href": "new_pages/diagrams.html#preparation", "title": "35  Diagrams and charts", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n DiagrammeR, # for flow diagrams\n networkD3, # For alluvial/Sankey diagrams\n tidyverse) # data management and visualization\n\n\n\nImport data\nMost of the content in this page does not require a dataset. However, in the Sankey diagram section, we will use the case linelist from a simulated Ebola epidemic. If you want to follow along for this part, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n DiagrammeR, # for flow diagrams\n networkD3, # For alluvial/Sankey diagrams\n tidyverse # data management and visualization\n ) \n\n\n\nImport data\nMost of the content in this page does not require a dataset. However, in the Sankey diagram section, we will use the case linelist from a simulated Ebola epidemic. If you want to follow along for this part, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.", "crumbs": [ "Data Visualization", "35  Diagrams and charts" @@ -3200,7 +3200,7 @@ "href": "new_pages/diagrams.html#flow-diagrams", "title": "35  Diagrams and charts", "section": "35.2 Flow diagrams", - "text": "35.2 Flow diagrams\nOne can use the R package DiagrammeR to create charts/flow charts. They can be static, or they can adjust somewhat dynamically based on changes in a dataset.\nTools\nThe function grViz() is used to create a “Graphviz” diagram. This function accepts a character string input containing instructions for making the diagram. Within that string, the instructions are written in a different language, called DOT - it is quite easy to learn the basics.\nBasic structure\n\nOpen the instructions grViz(\"\n\nSpecify directionality and name of the graph, and open brackets, e.g. digraph my_flow_chart {\nGraph statement (layout, rank direction)\n\nNodes statements (create nodes)\nEdges statements (gives links between nodes)\n\nClose the instructions }\")\n\n\nSimple examples\nBelow are two simple examples\nA very minimal example:\n\n# A minimal plot\nDiagrammeR::grViz(\"digraph {\n \ngraph[layout = dot, rankdir = LR]\n\na\nb\nc\n\na -> b -> c\n}\")\n\n\n\n\n\nAn example with perhaps a bit more applied public health context:\n\ngrViz(\" # All instructions are within a large character string\ndigraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name \n \n # graph statement\n #################\n graph [layout = dot,\n rankdir = TB,\n overlap = true,\n fontsize = 10]\n \n # nodes\n #######\n node [shape = circle, # shape = circle\n fixedsize = true\n width = 1.3] # width of circles\n \n Primary # names of nodes\n Secondary\n Tertiary\n\n # edges\n #######\n Primary -> Secondary [label = ' case transfer']\n Secondary -> Tertiary [label = ' case transfer']\n}\n\")\n\n\n\n\n\n\n\nSyntax\nBasic syntax\nNode names, or edge statements, can be separated with spaces, semicolons, or newlines.\nRank direction\nA plot can be re-oriented to move left-to-right by adjusting the rankdir argument within the graph statement. The default is TB (top-to-bottom), but it can be LR (left-to-right), RL, or BT.\nNode names\nNode names can be single words, as in the simple example above. To use multi-word names or special characters (e.g. parentheses, dashes), put the node name within single quotes (’ ’). It may be easier to have a short node name, and assign a label, as shown below within brackets [ ]. If you want to have a newline within the node’s name, you must do it via a label - use \\n in the node label within single quotes, as shown below.\nSubgroups\nWithin edge statements, subgroups can be created on either side of the edge with curly brackets ({ }). The edge then applies to all nodes in the bracket - it is a shorthand.\nLayouts\n\ndot (set rankdir to either TB, LR, RL, BT, )\nneato\n\ntwopi\n\ncirco\n\nNodes - editable attributes\n\nlabel (text, in single quotes if multi-word)\n\nfillcolor (many possible colors)\n\nfontcolor\n\nalpha (transparency 0-1)\n\nshape (ellipse, oval, diamond, egg, plaintext, point, square, triangle)\n\nstyle\n\nsides\n\nperipheries\n\nfixedsize (h x w)\n\nheight\n\nwidth\n\ndistortion\n\npenwidth (width of shape border)\n\nx (displacement left/right)\n\ny (displacement up/down)\n\nfontname\n\nfontsize\n\nicon\n\nEdges - editable attributes\n\narrowsize\n\narrowhead (normal, box, crow, curve, diamond, dot, inv, none, tee, vee)\n\narrowtail\n\ndir (direction, )\n\nstyle (dashed, …)\n\ncolor\n\nalpha\n\nheadport (text in front of arrowhead)\n\ntailport (text in behind arrowtail)\n\nfontname\n\nfontsize\n\nfontcolor\n\npenwidth (width of arrow)\n\nminlen (minimum length)\n\nColor names: hexadecimal values or ‘X11’ color names, see here for X11 details\n\n\nComplex examples\nThe example below expands on the surveillance_diagram, adding complex node names, grouped edges, colors and styling\nDiagrammeR::grViz(\" # All instructions are within a large character string\ndigraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name \n \n # graph statement\n #################\n graph [layout = dot,\n rankdir = TB, # layout top-to-bottom\n fontsize = 10]\n \n\n # nodes (circles)\n #################\n node [shape = circle, # shape = circle\n fixedsize = true\n width = 1.3] \n \n Primary [label = 'Primary\\nFacility'] \n Secondary [label = 'Secondary\\nFacility'] \n Tertiary [label = 'Tertiary\\nFacility'] \n SC [label = 'Surveillance\\nCoordination',\n fontcolor = darkgreen] \n \n # edges\n #######\n Primary -> Secondary [label = ' case transfer',\n fontcolor = red,\n color = red]\n Secondary -> Tertiary [label = ' case transfer',\n fontcolor = red,\n color = red]\n \n # grouped edge\n {Primary Secondary Tertiary} -> SC [label = 'case reporting',\n fontcolor = darkgreen,\n color = darkgreen,\n style = dashed]\n}\n\")\n\n\n\n\n\n\nSub-graph clusters\nTo group nodes into boxed clusters, put them within the same named subgraph (subgraph name {}). To have each subgraph identified within a bounding box, begin the name of the subgraph with “cluster”, as shown with the 4 boxes below.\nDiagrammeR::grViz(\" # All instructions are within a large character string\ndigraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name \n \n # graph statement\n #################\n graph [layout = dot,\n rankdir = TB, \n overlap = true,\n fontsize = 10]\n \n\n # nodes (circles)\n #################\n node [shape = circle, # shape = circle\n fixedsize = true\n width = 1.3] # width of circles\n \n subgraph cluster_passive {\n Primary [label = 'Primary\\nFacility'] \n Secondary [label = 'Secondary\\nFacility'] \n Tertiary [label = 'Tertiary\\nFacility'] \n SC [label = 'Surveillance\\nCoordination',\n fontcolor = darkgreen] \n }\n \n # nodes (boxes)\n ###############\n node [shape = box, # node shape\n fontname = Helvetica] # text font in node\n \n subgraph cluster_active {\n Active [label = 'Active\\nSurveillance'] \n HCF_active [label = 'HCF\\nActive Search']\n }\n \n subgraph cluster_EBD {\n EBS [label = 'Event-Based\\nSurveillance (EBS)'] \n 'Social Media'\n Radio\n }\n \n subgraph cluster_CBS {\n CBS [label = 'Community-Based\\nSurveillance (CBS)']\n RECOs\n }\n\n \n # edges\n #######\n {Primary Secondary Tertiary} -> SC [label = 'case reporting']\n\n Primary -> Secondary [label = 'case transfer',\n fontcolor = red]\n Secondary -> Tertiary [label = 'case transfer',\n fontcolor = red]\n \n HCF_active -> Active\n \n {'Social Media' Radio} -> EBS\n \n RECOs -> CBS\n}\n\")\n\n\n\n\n\n\n\nNode shapes\nThe example below, borrowed from this tutorial, shows applied node shapes and a shorthand for serial edge connections\n\nDiagrammeR::grViz(\"digraph {\n\ngraph [layout = dot, rankdir = LR]\n\n# define the global styles of the nodes. We can override these in box if we wish\nnode [shape = rectangle, style = filled, fillcolor = Linen]\n\ndata1 [label = 'Dataset 1', shape = folder, fillcolor = Beige]\ndata2 [label = 'Dataset 2', shape = folder, fillcolor = Beige]\nprocess [label = 'Process \\n Data']\nstatistical [label = 'Statistical \\n Analysis']\nresults [label= 'Results']\n\n# edge definitions with the node IDs\n{data1 data2} -> process -> statistical -> results\n}\")\n\n\n\n\n\n\n\nOutputs\nHow to handle and save outputs\n\nOutputs will appear in RStudio’s Viewer pane, by default in the lower-right alongside Files, Plots, Packages, and Help.\n\nTo export you can “Save as image” or “Copy to clipboard” from the Viewer. The graphic will adjust to the specified size.\n\n\n\nParameterized figures\nHere is a quote from this tutorial: https://mikeyharper.uk/flowcharts-in-r-using-diagrammer/\n“Parameterized figures: A great benefit of designing figures within R is that we are able to connect the figures directly with our analysis by reading R values directly into our flowcharts. For example, suppose you have created a filtering process which removes values after each stage of a process, you can have a figure show the number of values left in the dataset after each stage of your process. To do this we, you can use the @@X symbol directly within the figure, then refer to this in the footer of the plot using [X]:, where X is the a unique numeric index.”\nWe encourage you to review this tutorial if parameterization is something you are interested in.", + "text": "35.2 Flow diagrams\nOne can use the R package DiagrammeR to create charts/flow charts. They can be static, or they can adjust somewhat dynamically based on changes in a dataset.\nTools\nThe function grViz() is used to create a “Graphviz” diagram. This function accepts a character string input containing instructions for making the diagram. Within that string, the instructions are written in a different language, called DOT - it is quite easy to learn the basics.\nBasic structure\n\nOpen the instructions grViz(\"\nSpecify directionality and name of the graph, and open brackets, e.g. digraph my_flow_chart {\nGraph statement (layout, rank direction)\nNodes statements (create nodes)\nEdges statements (gives links between nodes)\nClose the instructions }\")\n\n\nSimple examples\nBelow are two simple examples\nA very minimal example:\n\n# A minimal plot\nDiagrammeR::grViz(\"digraph {\n \ngraph[layout = dot, rankdir = LR]\n\na\nb\nc\n\na -> b -> c\n}\")\n\n\n\n\n\nAn example with perhaps a bit more applied public health context:\n\ngrViz(\" # All instructions are within a large character string\ndigraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name \n \n # graph statement\n #################\n graph [layout = dot,\n rankdir = TB,\n overlap = true,\n fontsize = 10]\n \n # nodes\n #######\n node [shape = circle, # shape = circle\n fixedsize = true\n width = 1.3] # width of circles\n \n Primary # names of nodes\n Secondary\n Tertiary\n\n # edges\n #######\n Primary -> Secondary [label = ' case transfer']\n Secondary -> Tertiary [label = ' case transfer']\n}\n\")\n\n\n\n\n\n\n\nSyntax\nBasic syntax\nNode names, or edge statements, can be separated with spaces, semicolons, or newlines.\nRank direction\nA plot can be re-oriented to move left-to-right by adjusting the rankdir argument within the graph statement. The default is TB (top-to-bottom), but it can be LR (left-to-right), RL (right-to-left), or BT (bottom-to-top).\nNode names\nNode names can be single words, as in the simple example above. To use multi-word names or special characters (e.g. parentheses, dashes), put the node name within single quotes (’ ’). It may be easier to have a short node name, and assign a label, as shown below within brackets [ ]. If you want to have a newline within the node’s name, you must do it via a label - use \\n in the node label within single quotes, as shown below.\nSubgroups\nWithin edge statements, subgroups can be created on either side of the edge with curly brackets ({ }). The edge then applies to all nodes in the bracket - it is a shorthand.\nLayouts\n\ndot (set rankdir to either TB, LR, RL, BT, )\nneato\n\ntwopi\n\ncirco\n\nNodes - editable attributes\n\nlabel (text, in single quotes if multi-word)\n\nfillcolor (many possible colors)\n\nfontcolor\n\nalpha (transparency 0-1)\n\nshape (ellipse, oval, diamond, egg, plaintext, point, square, triangle)\n\nstyle\n\nsides\n\nperipheries\n\nfixedsize (h x w)\n\nheight\n\nwidth\n\ndistortion\n\npenwidth (width of shape border)\n\nx (displacement left/right)\n\ny (displacement up/down)\n\nfontname\n\nfontsize\n\nicon\n\nEdges - editable attributes\n\narrowsize\n\narrowhead (normal, box, crow, curve, diamond, dot, inv, none, tee, vee)\n\narrowtail\n\ndir (direction, )\n\nstyle (dashed, …)\n\ncolor\n\nalpha\n\nheadport (text in front of arrowhead)\n\ntailport (text in behind arrowtail)\n\nfontname\n\nfontsize\n\nfontcolor\n\npenwidth (width of arrow)\n\nminlen (minimum length)\n\nColor names: hexadecimal values or ‘X11’ color names, see here for X11 details\n\n\nComplex examples\nThe example below expands on the surveillance_diagram, adding complex node names, grouped edges, colors and styling\n\nDiagrammeR::grViz(\" # All instructions are within a large character string\ndigraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name \n \n # graph statement\n #################\n graph [layout = dot,\n rankdir = TB, # layout top-to-bottom\n fontsize = 10]\n \n\n # nodes (circles)\n #################\n node [shape = circle, # shape = circle\n fixedsize = true\n width = 1.3] \n \n Primary [label = 'Primary\\nFacility'] \n Secondary [label = 'Secondary\\nFacility'] \n Tertiary [label = 'Tertiary\\nFacility'] \n SC [label = 'Surveillance\\nCoordination',\n fontcolor = darkgreen] \n \n # edges\n #######\n Primary -> Secondary [label = ' case transfer',\n fontcolor = red,\n color = red]\n Secondary -> Tertiary [label = ' case transfer',\n fontcolor = red,\n color = red]\n \n # grouped edge\n {Primary Secondary Tertiary} -> SC [label = 'case reporting',\n fontcolor = darkgreen,\n color = darkgreen,\n style = dashed]\n}\n\")\n\n\n\n\n\n\n\nSub-graph clusters\nTo group nodes into boxed clusters, put them within the same named subgraph (subgraph name {}). To have each subgraph identified within a bounding box, begin the name of the subgraph with “cluster”, as shown with the 4 boxes below.\n\nDiagrammeR::grViz(\" # All instructions are within a large character string\ndigraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name \n \n # graph statement\n #################\n graph [layout = dot,\n rankdir = TB, \n overlap = true,\n fontsize = 10]\n \n\n # nodes (circles)\n #################\n node [shape = circle, # shape = circle\n fixedsize = true\n width = 1.3] # width of circles\n \n subgraph cluster_passive {\n Primary [label = 'Primary\\nFacility'] \n Secondary [label = 'Secondary\\nFacility'] \n Tertiary [label = 'Tertiary\\nFacility'] \n SC [label = 'Surveillance\\nCoordination',\n fontcolor = darkgreen] \n }\n \n # nodes (boxes)\n ###############\n node [shape = box, # node shape\n fontname = Helvetica] # text font in node\n \n subgraph cluster_active {\n Active [label = 'Active\\nSurveillance'] \n HCF_active [label = 'HCF\\nActive Search']\n }\n \n subgraph cluster_EBD {\n EBS [label = 'Event-Based\\nSurveillance (EBS)'] \n 'Social Media'\n Radio\n }\n \n subgraph cluster_CBS {\n CBS [label = 'Community-Based\\nSurveillance (CBS)']\n RECOs\n }\n\n \n # edges\n #######\n {Primary Secondary Tertiary} -> SC [label = 'case reporting']\n\n Primary -> Secondary [label = 'case transfer',\n fontcolor = red]\n Secondary -> Tertiary [label = 'case transfer',\n fontcolor = red]\n \n HCF_active -> Active\n \n {'Social Media' Radio} -> EBS\n \n RECOs -> CBS\n}\n\")\n\n\n\n\n\n\n\nNode shapes\nThe example below, borrowed from this tutorial, shows applied node shapes and a shorthand for serial edge connections\n\nsaved_plot <- DiagrammeR::grViz(\"digraph {\n\ngraph [layout = dot, rankdir = LR]\n\n# define the global styles of the nodes. We can override these in box if we wish\nnode [shape = rectangle, style = filled, fillcolor = Linen]\n\ndata1 [label = 'Dataset 1', shape = folder, fillcolor = Beige]\ndata2 [label = 'Dataset 2', shape = folder, fillcolor = Beige]\nprocess [label = 'Process \\n Data']\nstatistical [label = 'Statistical \\n Analysis']\nresults [label= 'Results']\n\n# edge definitions with the node IDs\n{data1 data2} -> process -> statistical -> results\n}\")\n\nsaved_plot\n\n\n\n\n\n\n\nOutputs\nHow to handle and save outputs:\n\nOutputs will appear in RStudio’s Viewer pane, by default in the lower-right alongside Files, Plots, Packages, and Help.\nTo export you can “Save as image” or “Copy to clipboard” from the Viewer. The graphic will adjust to the specified size.\n\n\n\nParameterized figures\nHere is a quote from this tutorial, Data-driven flowcharts in R using DiagrammeR.\n“Parameterized figures: A great benefit of designing figures within R is that we are able to connect the figures directly with our analysis by reading R values directly into our flowcharts. For example, suppose you have created a filtering process which removes values after each stage of a process, you can have a figure show the number of values left in the dataset after each stage of your process. To do this we, you can use the @@X symbol directly within the figure, then refer to this in the footer of the plot using [X]:, where X is the a unique numeric index.”\nWe encourage you to review this tutorial if parameterization is something you are interested in.", "crumbs": [ "Data Visualization", "35  Diagrams and charts" @@ -3211,7 +3211,7 @@ "href": "new_pages/diagrams.html#alluvialsankey-diagrams", "title": "35  Diagrams and charts", "section": "35.3 Alluvial/Sankey Diagrams", - "text": "35.3 Alluvial/Sankey Diagrams\n\nLoad packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\nWe load the networkD3 package to produce the diagram, and also tidyverse for the data preparation steps.\n\npacman::p_load(\n networkD3,\n tidyverse)\n\n\n\nPlotting from dataset\nPlotting the connections in a dataset. Below we demonstrate using this package on the case linelist. Here is an online tutorial.\nWe begin by getting the case counts for each unique age category and hospital combination. We’ve removed values with missing age category for clarity. We also re-label the hospital and age_cat columns as source and target respectively. These will be the two sides of the alluvial diagram.\n\n# counts by hospital and age category\nlinks <- linelist %>% \n drop_na(age_cat) %>% \n select(hospital, age_cat) %>%\n count(hospital, age_cat) %>% \n rename(source = hospital,\n target = age_cat)\n\nThe dataset now look like this:\n\n\n\n\n\n\nNow we create a data frame of all the diagram nodes, under the column name. This consists of all the values for hospital and age_cat. Note that we ensure they are all class Character before combining them. and adjust the ID columns to be numbers instead of labels:\n\n# The unique node names\nnodes <- data.frame(\n name=c(as.character(links$source), as.character(links$target)) %>% \n unique()\n )\n\nnodes # print\n\n name\n1 Central Hospital\n2 Military Hospital\n3 Missing\n4 Other\n5 Port Hospital\n6 St. Mark's Maternity Hospital (SMMH)\n7 0-4\n8 5-9\n9 10-14\n10 15-19\n11 20-29\n12 30-49\n13 50-69\n14 70+\n\n\nThe we edit the links data frame, which we created above with count(). We add two numeric columns IDsource and IDtarget which will actually reflect/create the links between the nodes. These columns will hold the rownumbers (position) of the source and target nodes. 1 is subtracted so that these position numbers begin at 0 (not 1).\n\n# match to numbers, not names\nlinks$IDsource <- match(links$source, nodes$name)-1 \nlinks$IDtarget <- match(links$target, nodes$name)-1\n\nThe links dataset now looks like this:\n\n\n\n\n\n\nNow plot the Sankey diagram with sankeyNetwork(). You can read more about each argument by running ?sankeyNetwork in the console. Note that unless you set iterations = 0 the order of your nodes may not be as expected.\n\n# plot\n######\np <- sankeyNetwork(\n Links = links,\n Nodes = nodes,\n Source = \"IDsource\",\n Target = \"IDtarget\",\n Value = \"n\",\n NodeID = \"name\",\n units = \"TWh\",\n fontSize = 12,\n nodeWidth = 30,\n iterations = 0) # ensure node order is as in data\np\n\n\n\n\n\nHere is an example where the patient Outcome is included as well. Note in the data preparation step we have to calculate the counts of cases between age and hospital, and separately between hospital and outcome - and then bind all these counts together with bind_rows().\n\n# counts by hospital and age category\nage_hosp_links <- linelist %>% \n drop_na(age_cat) %>% \n select(hospital, age_cat) %>%\n count(hospital, age_cat) %>% \n rename(source = age_cat, # re-name\n target = hospital)\n\nhosp_out_links <- linelist %>% \n drop_na(age_cat) %>% \n select(hospital, outcome) %>% \n count(hospital, outcome) %>% \n rename(source = hospital, # re-name\n target = outcome)\n\n# combine links\nlinks <- bind_rows(age_hosp_links, hosp_out_links)\n\n# The unique node names\nnodes <- data.frame(\n name=c(as.character(links$source), as.character(links$target)) %>% \n unique()\n )\n\n# Create id numbers\nlinks$IDsource <- match(links$source, nodes$name)-1 \nlinks$IDtarget <- match(links$target, nodes$name)-1\n\n# plot\n######\np <- sankeyNetwork(Links = links,\n Nodes = nodes,\n Source = \"IDsource\",\n Target = \"IDtarget\",\n Value = \"n\",\n NodeID = \"name\",\n units = \"TWh\",\n fontSize = 12,\n nodeWidth = 30,\n iterations = 0)\np\n\n\n\n\n\nhttps://www.displayr.com/sankey-diagrams-r/", + "text": "35.3 Alluvial/Sankey Diagrams\n\nLoad packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\nWe load the ggalluvial package to produce the diagram, and also tidyverse for the data preparation steps.\n\npacman::p_load(\n ggalluvial, # For alluvial/Sankey diagrams\n tidyverse # data management and visualization\n ) \n\n\n\nPlotting from dataset\nPlotting the connections in a dataset. Below we demonstrate using this package on the case linelist. Here is an online tutorial.\nWe begin by getting the case counts for each unique gender, source, vomit and outcome combination. We’ve removed missing values for clarity.\n\n# counts by hospital and age category\nlinks <- linelist %>% \n select(gender, source, vomit, outcome) %>%\n count(gender, source, vomit, outcome) %>%\n drop_na() #It is not necessary to drop NA values, we are just doing this for display purposes\n\nThe dataset now look like this:\n\n\n\n\n\n\nNow plot the Sankey diagram with geom_alluvium() and geom_stratum(). You can read more about each argument by running ?geom_alluvium and ?geom_stratum in the console.\nIn this plot we will look at source, vomit and colour our flows by gender.\n\n# plot\nggplot(data = links,\n mapping = aes(y = n,\n axis1 = source,\n axis2 = vomit)) +\n geom_alluvium(aes(fill = gender)) +\n geom_stratum(width = 1/12, fill = \"black\", color = \"grey\") +\n geom_label(stat = \"stratum\", aes(label = after_stat(stratum))) +\n scale_x_discrete(limits = c(\"Hospital\", \"Age category\"), expand = c(.05, .05)) +\n scale_fill_brewer(type = \"qual\", palette = \"Set1\") +\n theme_void()\n\n\n\n\n\n\n\n\nTo add an additional axis, we can put in the argument axis3 within mapping = aes(). If we would like to move the plot from horizontal to vertical, we can use the argument coord_flip().\nHere is an example where the outcome is included as well in the argument axis3 = outcome\n\n# plot\nggplot(data = links,\n mapping = aes(y = n,\n axis1 = source,\n axis2 = vomit,\n axis3 = outcome)) +\n geom_alluvium(aes(fill = gender)) +\n geom_stratum(width = 1/12, fill = \"black\", color = \"grey\") +\n geom_label(stat = \"stratum\", aes(label = after_stat(stratum))) +\n scale_x_discrete(limits = c(\"Hospital\", \"Age category\"), expand = c(.05, .05)) +\n scale_fill_brewer(type = \"qual\", palette = \"Set1\") +\n theme_void() +\n coord_flip()", "crumbs": [ "Data Visualization", "35  Diagrams and charts" @@ -3222,7 +3222,7 @@ "href": "new_pages/diagrams.html#event-timelines", "title": "35  Diagrams and charts", "section": "35.4 Event timelines", - "text": "35.4 Event timelines\nTo make a timeline showing specific events, you can use the vistime package.\nSee this vignette\n\n# load package\npacman::p_load(vistime, # make the timeline\n plotly # for interactive visualization\n )\n\nHere is the events dataset we begin with:\n\n\n\n\n\n\n\np <- vistime(data) # apply vistime\n\nlibrary(plotly)\n\n# step 1: transform into a list\npp <- plotly_build(p)\n\n# step 2: Marker size\nfor(i in 1:length(pp$x$data)){\n if(pp$x$data[[i]]$mode == \"markers\") pp$x$data[[i]]$marker$size <- 10\n}\n\n# step 3: text size\nfor(i in 1:length(pp$x$data)){\n if(pp$x$data[[i]]$mode == \"text\") pp$x$data[[i]]$textfont$size <- 10\n}\n\n\n# step 4: text position\nfor(i in 1:length(pp$x$data)){\n if(pp$x$data[[i]]$mode == \"text\") pp$x$data[[i]]$textposition <- \"right\"\n}\n\n#print\npp", + "text": "35.4 Event timelines\nTo make a timeline showing specific events, you can use the vistime package.\nSee this vignette\n\n# load package\npacman::p_load(\n vistime, # make the timeline\n plotly # for interactive visualization\n )\n\nHere is the events dataset we begin with:\n\n\n\n\n\n\n\np <- vistime(data) # apply vistime\n\n# step 1: transform into a list\npp <- plotly_build(p)\n\n# step 2: Marker size\nfor(i in 1:length(pp$x$data)){\n if(pp$x$data[[i]]$mode == \"markers\") pp$x$data[[i]]$marker$size <- 10\n}\n\n# step 3: text size\nfor(i in 1:length(pp$x$data)){\n if(pp$x$data[[i]]$mode == \"text\") pp$x$data[[i]]$textfont$size <- 10\n}\n\n\n# step 4: text position\nfor(i in 1:length(pp$x$data)){\n if(pp$x$data[[i]]$mode == \"text\") pp$x$data[[i]]$textposition <- \"right\"\n}\n\n#print\npp", "crumbs": [ "Data Visualization", "35  Diagrams and charts" @@ -3266,7 +3266,7 @@ "href": "new_pages/combination_analysis.html#preparation", "title": "36  Combinations analysis", "section": "", - "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n tidyverse, # data management and visualization\n UpSetR, # special package for combination plots\n ggupset) # special package for combination plots\n\n\n\n\nImport data\nTo begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import case linelist \nlinelist_sym <- import(\"linelist_cleaned.rds\")\n\nThis linelist includes five “yes/no” variables on reported symptoms. We will need to transform these variables a bit to use the ggupset package to make our plot. View the data (scroll to the right to see the symptoms variables).\n\n\n\n\n\n\n\n\n\nRe-format values\nTo align with the format expected by ggupset we convert the “yes” and “no” the the actual symptom name, using case_when() from dplyr. If “no”, we set the value as blank, so the values are either NA or the symptom.\n\n# create column with the symptoms named, separated by semicolons\nlinelist_sym_1 <- linelist_sym %>% \n\n # convert the \"yes\" and \"no\" values into the symptom name itself\n # if old value is \"yes\", new value is \"fever\", otherwise set to missing (NA)\nmutate(fever = ifelse(fever == \"yes\", \"fever\", NA), \n chills = ifelse(chills == \"yes\", \"chills\", NA),\n cough = ifelse(cough == \"yes\", \"cough\", NA),\n aches = ifelse(aches == \"yes\", \"aches\", NA),\n vomit = ifelse(vomit == \"yes\", \"vomit\", NA))\n\nNow we make two final columns:\n\nConcatenating (gluing together) all the symptoms of the patient (a character column)\n\nConvert the above column to class list, so it can be accepted by ggupset to make the plot\n\nSee the page on Characters and strings to learn more about the unite() function from stringr\n\nlinelist_sym_1 <- linelist_sym_1 %>% \n unite(col = \"all_symptoms\",\n c(fever, chills, cough, aches, vomit), \n sep = \"; \",\n remove = TRUE,\n na.rm = TRUE) %>% \n mutate(\n # make a copy of all_symptoms column, but of class \"list\" (which is required to use ggupset() in next step)\n all_symptoms_list = as.list(strsplit(all_symptoms, \"; \"))\n )\n\nView the new data. Note the two columns towards the right end - the pasted combined values, and the list", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # for importing data\n here, # for relative filepaths\n tidyverse, # data management and visualization\n UpSetR, # special package for combination plots\n ggupset # special package for combination plots\n ) \n\n\n\n\nImport data\nTo begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).\n\n# import case linelist \nlinelist_sym <- import(\"linelist_cleaned.rds\")\n\nThis linelist includes five “yes/no” variables on reported symptoms. We will need to transform these variables a bit to use the ggupset package to make our plot. View the data (scroll to the right to see the symptoms variables).\n\n\n\n\n\n\n\n\n\nRe-format values\nTo align with the format expected by ggupset we convert the “yes” and “no” the the actual symptom name, using case_when() from dplyr. If “no”, we set the value as blank, so the values are either NA or the symptom.\n\n# create column with the symptoms named, separated by semicolons\nlinelist_sym_1 <- linelist_sym %>% \n\n # convert the \"yes\" and \"no\" values into the symptom name itself\n # if old value is \"yes\", new value is \"fever\", otherwise set to missing (NA)\nmutate(fever = ifelse(fever == \"yes\", \"fever\", NA), \n chills = ifelse(chills == \"yes\", \"chills\", NA),\n cough = ifelse(cough == \"yes\", \"cough\", NA),\n aches = ifelse(aches == \"yes\", \"aches\", NA),\n vomit = ifelse(vomit == \"yes\", \"vomit\", NA))\n\nNow we make two final columns:\n\nConcatenating (gluing together) all the symptoms of the patient (a character column).\nConvert the above column to class list, so it can be accepted by ggupset to make the plot.\n\nSee the page on Characters and strings to learn more about the unite() function from stringr.\n\nlinelist_sym_1 <- linelist_sym_1 %>% \n unite(col = \"all_symptoms\",\n c(fever, chills, cough, aches, vomit), \n sep = \"; \",\n remove = TRUE,\n na.rm = TRUE) %>% \n mutate(\n # make a copy of all_symptoms column, but of class \"list\" (which is required to use ggupset() in next step)\n all_symptoms_list = as.list(strsplit(all_symptoms, \"; \"))\n )\n\nView the new data. Note the two columns towards the right end - the pasted combined values, and the list.", "crumbs": [ "Data Visualization", "36  Combinations analysis" @@ -3277,7 +3277,7 @@ "href": "new_pages/combination_analysis.html#ggupset", "title": "36  Combinations analysis", "section": "36.2 ggupset", - "text": "36.2 ggupset\nLoad the package\n\npacman::p_load(ggupset)\n\nCreate the plot. We begin with a ggplot() and geom_bar(), but then we add the special function scale_x_upset() from the ggupset.\n\nggplot(\n data = linelist_sym_1,\n mapping = aes(x = all_symptoms_list)) +\ngeom_bar() +\nscale_x_upset(\n reverse = FALSE,\n n_intersections = 10,\n sets = c(\"fever\", \"chills\", \"cough\", \"aches\", \"vomit\"))+\nlabs(\n title = \"Signs & symptoms\",\n subtitle = \"10 most frequent combinations of signs and symptoms\",\n caption = \"Caption here.\",\n x = \"Symptom combination\",\n y = \"Frequency in dataset\")\n\n\n\n\n\n\n\n\nMore information on ggupset can be found online or offline in the package documentation in your RStudio Help tab ?ggupset.", + "text": "36.2 ggupset\nLoad the package\n\npacman::p_load(ggupset)\n\nCreate the plot. We begin with a ggplot() and geom_bar(), but then we add the special function scale_x_upset() from the ggupset.\n\nggplot(\n data = linelist_sym_1,\n mapping = aes(x = all_symptoms_list)) +\ngeom_bar() +\nscale_x_upset(\n reverse = FALSE,\n n_intersections = 10,\n sets = c(\"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")) +\nlabs(\n title = \"Signs & symptoms\",\n subtitle = \"10 most frequent combinations of signs and symptoms\",\n caption = \"Caption here.\",\n x = \"Symptom combination\",\n y = \"Frequency in dataset\")\n\n\n\n\n\n\n\n\nMore information on ggupset can be found online or offline in the package documentation in your RStudio Help tab ?ggupset.", "crumbs": [ "Data Visualization", "36  Combinations analysis" @@ -3288,7 +3288,7 @@ "href": "new_pages/combination_analysis.html#upsetr", "title": "36  Combinations analysis", "section": "36.3 UpSetR", - "text": "36.3 UpSetR\nThe UpSetR package allows more customization of the plot, but it can be more difficult to execute:\nLoad package\n\npacman::p_load(UpSetR)\n\nData cleaning\nWe must convert the linelist symptoms values to 1 / 0.\n\nlinelist_sym_2 <- linelist_sym %>% \n # convert the \"yes\" and \"no\" values into 1s and 0s\n mutate(fever = ifelse(fever == \"yes\", 1, 0), \n chills = ifelse(chills == \"yes\", 1, 0),\n cough = ifelse(cough == \"yes\", 1, 0),\n aches = ifelse(aches == \"yes\", 1, 0),\n vomit = ifelse(vomit == \"yes\", 1, 0))\n\nIf you are interested in a more efficient command, you can take advantage of the +() function, which converts to 1s and 0s based on a logical statement. This command utilizes the across() function to change multiple columns at once (read more in Cleaning data and core functions).\n\n# Efficiently convert \"yes\" to 1 and 0\nlinelist_sym_2 <- linelist_sym %>% \n \n # convert the \"yes\" and \"no\" values into 1s and 0s\n mutate(across(c(fever, chills, cough, aches, vomit), .fns = ~+(.x == \"yes\")))\n\nNow make the plot using the custom function upset() - using only the symptoms columns. You must designate which “sets” to compare (the names of the symptom columns). Alternatively, use nsets = and order.by = \"freq\" to only show the top X combinations.\n\n# Make the plot\nlinelist_sym_2 %>% \n UpSetR::upset(\n sets = c(\"fever\", \"chills\", \"cough\", \"aches\", \"vomit\"),\n order.by = \"freq\",\n sets.bar.color = c(\"blue\", \"red\", \"yellow\", \"darkgreen\", \"orange\"), # optional colors\n empty.intersections = \"on\",\n # nsets = 3,\n number.angles = 0,\n point.size = 3.5,\n line.size = 2, \n mainbar.y.label = \"Symptoms Combinations\",\n sets.x.label = \"Patients with Symptom\")", + "text": "36.3 UpSetR\nThe UpSetR package allows more customization of the plot, but it can be more difficult to execute:\nLoad package\n\npacman::p_load(UpSetR)\n\nData cleaning\nWe must convert the linelist symptoms values to 1 / 0.\n\nlinelist_sym_2 <- linelist_sym %>% \n # convert the \"yes\" and \"no\" values into 1s and 0s\n mutate(fever = ifelse(fever == \"yes\", 1, 0), \n chills = ifelse(chills == \"yes\", 1, 0),\n cough = ifelse(cough == \"yes\", 1, 0),\n aches = ifelse(aches == \"yes\", 1, 0),\n vomit = ifelse(vomit == \"yes\", 1, 0))\n\nIf you are interested in a more efficient command, you can take advantage of the +() function, which converts to 1s and 0s based on a logical statement. This command utilizes the across() function to change multiple columns at once (read more in Cleaning data and core functions).\n\n# Efficiently convert \"yes\" to 1 and 0\nlinelist_sym_2 <- linelist_sym %>% \n \n # convert the \"yes\" and \"no\" values into 1s and 0s\n mutate(across(c(fever, chills, cough, aches, vomit), .fns = ~+(.x == \"yes\")))\n\nNow make the plot using the custom function upset() - using only the symptoms columns. You must designate which “sets” to compare (the names of the symptom columns). Alternatively, use nsets = and order.by = \"freq\" to only show the top X combinations.\n\n# Make the plot\nlinelist_sym_2 %>% \n UpSetR::upset(\n sets = c(\"fever\", \"chills\", \"cough\", \"aches\", \"vomit\"),\n order.by = \"freq\",\n sets.bar.color = c(\"blue\", \"red\", \"yellow\", \"darkgreen\", \"orange\"), # optional colors\n empty.intersections = \"on\",\n # nsets = 3,\n number.angles = 0,\n point.size = 3.5,\n line.size = 2, \n mainbar.y.label = \"Symptoms Combinations\",\n sets.x.label = \"Patients with Symptom\"\n )", "crumbs": [ "Data Visualization", "36  Combinations analysis" @@ -3310,7 +3310,7 @@ "href": "new_pages/transmission_chains.html", "title": "37  Transmission chains", "section": "", - "text": "37.1 Overview\nThe primary tool to handle, analyse and visualise transmission chains and contact tracing data is the package epicontacts, developed by the folks at RECON. Try out the interactive plot below by hovering over the nodes for more information, dragging them to move them and clicking on them to highlight downstream cases.\nWarning in epicontacts::make_epicontacts(linelist = linelist, contacts =\ncontacts, : Cycle(s) detected in the contact network: this may be unwanted", + "text": "37.1 Overview\nThe primary tool to handle, analyse and visualise transmission chains and contact tracing data is the package epicontacts, developed by the folks at RECON. Try out the interactive plot below by hovering over the nodes for more information, dragging them to move them and clicking on them to highlight downstream cases.\nWarning: Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\n\n\nWarning in epicontacts::make_epicontacts(linelist = linelist, contacts =\ncontacts, : Cycle(s) detected in the contact network: this may be unwanted", "crumbs": [ "Data Visualization", "37  Transmission chains" @@ -3321,7 +3321,7 @@ "href": "new_pages/transmission_chains.html#preparation", "title": "37  Transmission chains", "section": "37.2 Preparation", - "text": "37.2 Preparation\n\nLoad packages\nFirst load the standard packages required for data import and manipulation. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n tidyverse, # Data management + ggplot2 graphics\n remotes # Package installation from github\n)\n\nYou will require the development version of epicontacts, which can be installed from github using the p_install_github() function from pacman. You only need to run this command below once, not every time you use the package (thereafter, you can use p_load() as usual).\n\npacman::p_install_gh(\"reconhub/epicontacts@timeline\")\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instructions in the Download handbook and data page. The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.xlsx\")\n\nThe first 50 rows of the linelist are displayed below. Of particular interest are the columns case_id, generation, infector, and source.\n\n\n\n\n\n\n\n\nCreating an epicontacts object\nWe then need to create an epicontacts object, which requires two types of data:\n\na linelist documenting cases where columns are variables and rows correspond to unique cases\na list of edges defining links between cases on the basis of their unique IDs (these can be contacts, transmission events, etc.)\n\nAs we already have a linelist, we just need to create a list of edges between cases, more specifically between their IDs. We can extract transmission links from the linelist by linking the infector column with the case_id column. At this point we can also add “edge properties”, by which we mean any variable describing the link between the two cases, not the cases themselves. For illustration, we will add a location variable describing the location of the transmission event, and a duration variable describing the duration of the contact in days.\nIn the code below, the dplyr function transmute is similar to mutate, except it only keeps the columns we have specified within the function. The drop_na function will filter out any rows where the specified columns have an NA value; in this case, we only want to keep the rows where the infector is known.\n\n## generate contacts\ncontacts <- linelist %>%\n transmute(\n infector = infector,\n case_id = case_id,\n location = sample(c(\"Community\", \"Nosocomial\"), n(), TRUE),\n duration = sample.int(10, n(), TRUE)\n ) %>%\n drop_na(infector)\n\nWe can now create the epicontacts object using the make_epicontacts function. We need to specify which column in the linelist points to the unique case identifier, as well as which columns in the contacts point to the unique identifiers of the cases involved in each link. These links are directional in that infection is going from the infector to the case, so we need to specify the from and to arguments accordingly. We therefore also set the directed argument to TRUE, which will affect future operations.\n\n## generate epicontacts object\nepic <- make_epicontacts(\n linelist = linelist,\n contacts = contacts,\n id = \"case_id\",\n from = \"infector\",\n to = \"case_id\",\n directed = TRUE\n)\n\nWarning in make_epicontacts(linelist = linelist, contacts = contacts, id =\n\"case_id\", : Cycle(s) detected in the contact network: this may be unwanted\n\n\nUpon examining the epicontacts objects, we can see that the case_id column in the linelist has been renamed to id and the case_id and infector columns in the contacts have been renamed to from and to. This ensures consistency in subsequent handling, visualisation and analysis operations.\n\n## view epicontacts object\nepic\n\n\n/// Epidemiological Contacts //\n\n // class: epicontacts\n // 5,888 cases in linelist; 3,800 contacts; directed \n\n // linelist\n\n# A tibble: 5,888 × 30\n id generation date_infection date_onset date_hospitalisation date_outcome\n <chr> <dbl> <date> <date> <date> <date> \n 1 5fe599 4 2014-05-08 2014-05-13 2014-05-15 NA \n 2 8689b7 4 NA 2014-05-13 2014-05-14 2014-05-18 \n 3 11f8ea 2 NA 2014-05-16 2014-05-18 2014-05-30 \n 4 b8812a 3 2014-05-04 2014-05-18 2014-05-20 NA \n 5 893f25 3 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 6 be99c8 3 2014-05-03 2014-05-22 2014-05-23 2014-05-24 \n 7 07e3e8 4 2014-05-22 2014-05-27 2014-05-29 2014-06-01 \n 8 369449 4 2014-05-28 2014-06-02 2014-06-03 2014-06-07 \n 9 f393b4 4 NA 2014-06-05 2014-06-06 2014-06-18 \n10 1389ca 4 NA 2014-06-05 2014-06-07 2014-06-09 \n# ℹ 5,878 more rows\n# ℹ 24 more variables: outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>,\n# age_years <dbl>, age_cat <fct>, age_cat5 <fct>, hospital <chr>, lon <dbl>,\n# lat <dbl>, infector <chr>, source <chr>, wt_kg <dbl>, ht_cm <dbl>,\n# ct_blood <dbl>, fever <chr>, chills <chr>, cough <chr>, aches <chr>,\n# vomit <chr>, temp <dbl>, time_admission <chr>, bmi <dbl>,\n# days_onset_hosp <dbl>\n\n // contacts\n\n# A tibble: 3,800 × 4\n from to location duration\n <chr> <chr> <chr> <int>\n 1 f547d6 5fe599 Community 2\n 2 f90f5f b8812a Nosocomial 1\n 3 11f8ea 893f25 Nosocomial 5\n 4 aec8ec be99c8 Community 3\n 5 893f25 07e3e8 Nosocomial 4\n 6 133ee7 369449 Community 10\n 7 996f3a 2978ac Nosocomial 7\n 8 133ee7 57a565 Community 8\n 9 37a6f6 fc15ef Community 2\n10 9f6884 2eaa9a Nosocomial 2\n# ℹ 3,790 more rows", + "text": "37.2 Preparation\n\nLoad packages\nFirst load the standard packages required for data import and manipulation. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n tidyverse, # Data management + ggplot2 graphics\n remotes # Package installation from github\n)\n\nYou will require the development version of epicontacts, which can be installed from github using the p_install_github() function from pacman. You only need to run this command below once, not every time you use the package (thereafter, you can use p_load() as usual).\n\npacman::p_install_gh(\"reconhub/epicontacts@timeline\")\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instructions in the Download handbook and data page. The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.xlsx\")\n\nThe first 50 rows of the linelist are displayed below. Of particular interest are the columns case_id, generation, infector, and source.\n\n\n\n\n\n\n\n\nCreating an epicontacts object\nWe then need to create an epicontacts object, which requires two types of data:\n\na linelist documenting cases where columns are variables and rows correspond to unique cases.\na list of edges defining links between cases on the basis of their unique IDs (these can be contacts, transmission events, etc.).\n\nAs we already have a linelist, we just need to create a list of edges between cases, more specifically between their IDs. We can extract transmission links from the linelist by linking the infector column with the case_id column. At this point we can also add “edge properties”, by which we mean any variable describing the link between the two cases, not the cases themselves. For illustration, we will add a location variable describing the location of the transmission event, and a duration variable describing the duration of the contact in days.\nIn the code below, the dplyr function transmute is similar to mutate, except it only keeps the columns we have specified within the function. The drop_na function will filter out any rows where the specified columns have an NA value; in this case, we only want to keep the rows where the infector is known.\n\n## generate contacts\ncontacts <- linelist %>%\n transmute(\n infector = infector,\n case_id = case_id,\n location = sample(c(\"Community\", \"Nosocomial\"), n(), TRUE),\n duration = sample.int(10, n(), TRUE)\n ) %>%\n drop_na(infector)\n\nWe can now create the epicontacts object using the make_epicontacts function. We need to specify which column in the linelist points to the unique case identifier, as well as which columns in the contacts point to the unique identifiers of the cases involved in each link. These links are directional in that infection is going from the infector to the case, so we need to specify the from and to arguments accordingly. We therefore also set the directed argument to TRUE, which will affect future operations.\n\n## generate epicontacts object\nepic <- make_epicontacts(\n linelist = linelist,\n contacts = contacts,\n id = \"case_id\",\n from = \"infector\",\n to = \"case_id\",\n directed = TRUE\n)\n\nWarning in make_epicontacts(linelist = linelist, contacts = contacts, id =\n\"case_id\", : Cycle(s) detected in the contact network: this may be unwanted\n\n\nUpon examining the epicontacts objects, we can see that the case_id column in the linelist has been renamed to id and the case_id and infector columns in the contacts have been renamed to from and to. This ensures consistency in subsequent handling, visualisation and analysis operations.\n\n## view epicontacts object\nepic\n\n\n/// Epidemiological Contacts //\n\n // class: epicontacts\n // 5,888 cases in linelist; 3,800 contacts; directed \n\n // linelist\n\n# A tibble: 5,888 × 30\n id generation date_infection date_onset date_hospitalisation date_outcome\n <chr> <dbl> <date> <date> <date> <date> \n 1 5fe599 4 2014-05-08 2014-05-13 2014-05-15 NA \n 2 8689b7 4 NA 2014-05-13 2014-05-14 2014-05-18 \n 3 11f8ea 2 NA 2014-05-16 2014-05-18 2014-05-30 \n 4 b8812a 3 2014-05-04 2014-05-18 2014-05-20 NA \n 5 893f25 3 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 6 be99c8 3 2014-05-03 2014-05-22 2014-05-23 2014-05-24 \n 7 07e3e8 4 2014-05-22 2014-05-27 2014-05-29 2014-06-01 \n 8 369449 4 2014-05-28 2014-06-02 2014-06-03 2014-06-07 \n 9 f393b4 4 NA 2014-06-05 2014-06-06 2014-06-18 \n10 1389ca 4 NA 2014-06-05 2014-06-07 2014-06-09 \n# ℹ 5,878 more rows\n# ℹ 24 more variables: outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>,\n# age_years <dbl>, age_cat <fct>, age_cat5 <fct>, hospital <chr>, lon <dbl>,\n# lat <dbl>, infector <chr>, source <chr>, wt_kg <dbl>, ht_cm <dbl>,\n# ct_blood <dbl>, fever <chr>, chills <chr>, cough <chr>, aches <chr>,\n# vomit <chr>, temp <dbl>, time_admission <chr>, bmi <dbl>,\n# days_onset_hosp <dbl>\n\n // contacts\n\n# A tibble: 3,800 × 4\n from to location duration\n <chr> <chr> <chr> <int>\n 1 f547d6 5fe599 Nosocomial 10\n 2 f90f5f b8812a Nosocomial 8\n 3 11f8ea 893f25 Nosocomial 5\n 4 aec8ec be99c8 Nosocomial 6\n 5 893f25 07e3e8 Nosocomial 3\n 6 133ee7 369449 Nosocomial 8\n 7 996f3a 2978ac Nosocomial 2\n 8 133ee7 57a565 Community 9\n 9 37a6f6 fc15ef Community 2\n10 9f6884 2eaa9a Community 7\n# ℹ 3,790 more rows", "crumbs": [ "Data Visualization", "37  Transmission chains" @@ -3332,7 +3332,7 @@ "href": "new_pages/transmission_chains.html#handling", "title": "37  Transmission chains", "section": "37.3 Handling", - "text": "37.3 Handling\n\nSubsetting\nThe subset() method for epicontacts objects allows for, among other things, filtering of networks based on properties of the linelist (“node attributes”) and the contacts database (“edge attributes”). These values must be passed as named lists to the respective argument. For example, in the code below we are keeping only the male cases in the linelist that have an infection date between April and July 2014 (dates are specified as ranges), and transmission links that occured in the hospital.\n\nsub_attributes <- subset(\n epic,\n node_attribute = list(\n gender = \"m\",\n date_infection = as.Date(c(\"2014-04-01\", \"2014-07-01\"))\n ), \n edge_attribute = list(location = \"Nosocomial\")\n)\nsub_attributes\n\n\n/// Epidemiological Contacts //\n\n // class: epicontacts\n // 69 cases in linelist; 1,912 contacts; directed \n\n // linelist\n\n# A tibble: 69 × 30\n id generation date_infection date_onset date_hospitalisation date_outcome\n <chr> <dbl> <date> <date> <date> <date> \n 1 5fe599 4 2014-05-08 2014-05-13 2014-05-15 NA \n 2 893f25 3 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 3 2978ac 4 2014-05-30 2014-06-06 2014-06-08 2014-06-15 \n 4 57a565 4 2014-05-28 2014-06-13 2014-06-15 NA \n 5 fc15ef 6 2014-06-14 2014-06-16 2014-06-17 2014-07-09 \n 6 99e8fa 7 2014-06-24 2014-06-28 2014-06-29 2014-07-09 \n 7 f327be 6 2014-06-14 2014-07-12 2014-07-13 2014-07-14 \n 8 90e5fe 5 2014-06-18 2014-07-13 2014-07-14 2014-07-16 \n 9 a47529 5 2014-06-13 2014-07-17 2014-07-18 2014-07-26 \n10 da8ecb 5 2014-06-20 2014-07-18 2014-07-20 2014-08-01 \n# ℹ 59 more rows\n# ℹ 24 more variables: outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>,\n# age_years <dbl>, age_cat <fct>, age_cat5 <fct>, hospital <chr>, lon <dbl>,\n# lat <dbl>, infector <chr>, source <chr>, wt_kg <dbl>, ht_cm <dbl>,\n# ct_blood <dbl>, fever <chr>, chills <chr>, cough <chr>, aches <chr>,\n# vomit <chr>, temp <dbl>, time_admission <chr>, bmi <dbl>,\n# days_onset_hosp <dbl>\n\n // contacts\n\n# A tibble: 1,912 × 4\n from to location duration\n <chr> <chr> <chr> <int>\n 1 f90f5f b8812a Nosocomial 1\n 2 11f8ea 893f25 Nosocomial 5\n 3 893f25 07e3e8 Nosocomial 4\n 4 996f3a 2978ac Nosocomial 7\n 5 9f6884 2eaa9a Nosocomial 2\n 6 a75c7f 7f5a01 Nosocomial 1\n 7 ab634e 99e8fa Nosocomial 7\n 8 b799eb bc2adf Nosocomial 1\n 9 a15e13 f327be Nosocomial 8\n10 ea3740 90e5fe Nosocomial 2\n# ℹ 1,902 more rows\n\n\nWe can use the thin function to either filter the linelist to include cases that are found in the contacts by setting the argument what = \"linelist\", or filter the contacts to include cases that are found in the linelist by setting the argument what = \"contacts\". In the code below, we are further filtering the epicontacts object to keep only the transmission links involving the male cases infected between April and July which we had filtered for above. We can see that only two known transmission links fit that specification.\n\nsub_attributes <- thin(sub_attributes, what = \"contacts\")\nnrow(sub_attributes$contacts)\n\n[1] 5\n\n\nIn addition to subsetting by node and edge attributes, networks can be pruned to only include components that are connected to certain nodes. The cluster_id argument takes a vector of case IDs and returns the linelist of individuals that are linked, directly or indirectly, to those IDs. In the code below, we can see that a total of 13 linelist cases are involved in the clusters containing 2ae019 and 71577a.\n\nsub_id <- subset(epic, cluster_id = c(\"2ae019\",\"71577a\"))\nnrow(sub_id$linelist)\n\n[1] 13\n\n\nThe subset() method for epicontacts objects also allows filtering by cluster size using the cs, cs_min and cs_max arguments. In the code below, we are keeping only the cases linked to clusters of 10 cases or larger, and can see that 271 linelist cases are involved in such clusters.\n\nsub_cs <- subset(epic, cs_min = 10)\nnrow(sub_cs$linelist)\n\n[1] 271\n\n\n\n\nAccessing IDs\nThe get_id() function retrieves information on case IDs in the dataset, and can be parameterized as follows:\n\nlinelist: IDs in the line list data\ncontacts: IDs in the contact dataset (“from” and “to” combined)\nfrom: IDs in the “from” column of contact datset\nto IDs in the “to” column of contact dataset\nall: IDs that appear anywhere in either dataset\ncommon: IDs that appear in both contacts dataset and line list\n\nFor example, what are the first ten IDs in the contacts dataset?\n\ncontacts_ids <- get_id(epic, \"contacts\")\nhead(contacts_ids, n = 10)\n\n [1] \"f547d6\" \"f90f5f\" \"11f8ea\" \"aec8ec\" \"893f25\" \"133ee7\" \"996f3a\" \"37a6f6\"\n [9] \"9f6884\" \"4802b1\"\n\n\nHow many IDs are found in both the linelist and the contacts?\n\nlength(get_id(epic, \"common\"))\n\n[1] 4352", + "text": "37.3 Handling\n\nSubsetting\nThe subset() method for epicontacts objects allows for, among other things, filtering of networks based on properties of the linelist (“node attributes”) and the contacts database (“edge attributes”). These values must be passed as named lists to the respective argument. For example, in the code below we are keeping only the male cases in the linelist that have an infection date between April and July 2014 (dates are specified as ranges), and transmission links that occured in the hospital.\n\nsub_attributes <- subset(\n epic,\n node_attribute = list(\n gender = \"m\",\n date_infection = as.Date(c(\"2014-04-01\", \"2014-07-01\"))\n ), \n edge_attribute = list(location = \"Nosocomial\")\n)\nsub_attributes\n\n\n/// Epidemiological Contacts //\n\n // class: epicontacts\n // 69 cases in linelist; 1,854 contacts; directed \n\n // linelist\n\n# A tibble: 69 × 30\n id generation date_infection date_onset date_hospitalisation date_outcome\n <chr> <dbl> <date> <date> <date> <date> \n 1 5fe599 4 2014-05-08 2014-05-13 2014-05-15 NA \n 2 893f25 3 2014-05-18 2014-05-21 2014-05-22 2014-05-29 \n 3 2978ac 4 2014-05-30 2014-06-06 2014-06-08 2014-06-15 \n 4 57a565 4 2014-05-28 2014-06-13 2014-06-15 NA \n 5 fc15ef 6 2014-06-14 2014-06-16 2014-06-17 2014-07-09 \n 6 99e8fa 7 2014-06-24 2014-06-28 2014-06-29 2014-07-09 \n 7 f327be 6 2014-06-14 2014-07-12 2014-07-13 2014-07-14 \n 8 90e5fe 5 2014-06-18 2014-07-13 2014-07-14 2014-07-16 \n 9 a47529 5 2014-06-13 2014-07-17 2014-07-18 2014-07-26 \n10 da8ecb 5 2014-06-20 2014-07-18 2014-07-20 2014-08-01 \n# ℹ 59 more rows\n# ℹ 24 more variables: outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>,\n# age_years <dbl>, age_cat <fct>, age_cat5 <fct>, hospital <chr>, lon <dbl>,\n# lat <dbl>, infector <chr>, source <chr>, wt_kg <dbl>, ht_cm <dbl>,\n# ct_blood <dbl>, fever <chr>, chills <chr>, cough <chr>, aches <chr>,\n# vomit <chr>, temp <dbl>, time_admission <chr>, bmi <dbl>,\n# days_onset_hosp <dbl>\n\n // contacts\n\n# A tibble: 1,854 × 4\n from to location duration\n <chr> <chr> <chr> <int>\n 1 f547d6 5fe599 Nosocomial 10\n 2 f90f5f b8812a Nosocomial 8\n 3 11f8ea 893f25 Nosocomial 5\n 4 aec8ec be99c8 Nosocomial 6\n 5 893f25 07e3e8 Nosocomial 3\n 6 133ee7 369449 Nosocomial 8\n 7 996f3a 2978ac Nosocomial 2\n 8 a75c7f 7f5a01 Nosocomial 7\n 9 8e104d ddddee Nosocomial 7\n10 ab634e 99e8fa Nosocomial 2\n# ℹ 1,844 more rows\n\n\nWe can use the thin function to either filter the linelist to include cases that are found in the contacts by setting the argument what = \"linelist\", or filter the contacts to include cases that are found in the linelist by setting the argument what = \"contacts\". In the code below, we are further filtering the epicontacts object to keep only the transmission links involving the male cases infected between April and July which we had filtered for above. We can see that only two known transmission links fit that specification.\n\nsub_attributes <- thin(sub_attributes, what = \"contacts\")\nnrow(sub_attributes$contacts)\n\n[1] 2\n\n\nIn addition to subsetting by node and edge attributes, networks can be pruned to only include components that are connected to certain nodes. The cluster_id argument takes a vector of case IDs and returns the linelist of individuals that are linked, directly or indirectly, to those IDs. In the code below, we can see that a total of 13 linelist cases are involved in the clusters containing 2ae019 and 71577a.\n\nsub_id <- subset(epic, cluster_id = c(\"2ae019\",\"71577a\"))\nnrow(sub_id$linelist)\n\n[1] 13\n\n\nThe subset() method for epicontacts objects also allows filtering by cluster size using the cs, cs_min and cs_max arguments. In the code below, we are keeping only the cases linked to clusters of 10 cases or larger, and can see that 271 linelist cases are involved in such clusters.\n\nsub_cs <- subset(epic, cs_min = 10)\nnrow(sub_cs$linelist)\n\n[1] 271\n\n\n\n\nAccessing IDs\nThe get_id() function retrieves information on case IDs in the dataset, and can be parameterized as follows:\n\nlinelist: IDs in the line list data.\ncontacts: IDs in the contact dataset (“from” and “to” combined).\nfrom: IDs in the “from” column of contact datset.\nto IDs in the “to” column of contact dataset.\nall: IDs that appear anywhere in either dataset.\ncommon: IDs that appear in both contacts dataset and line list.\n\nFor example, what are the first ten IDs in the contacts dataset?\n\ncontacts_ids <- get_id(epic, \"contacts\")\nhead(contacts_ids, n = 10)\n\n [1] \"f547d6\" \"f90f5f\" \"11f8ea\" \"aec8ec\" \"893f25\" \"133ee7\" \"996f3a\" \"37a6f6\"\n [9] \"9f6884\" \"4802b1\"\n\n\nHow many IDs are found in both the linelist and the contacts?\n\nlength(get_id(epic, \"common\"))\n\n[1] 4352", "crumbs": [ "Data Visualization", "37  Transmission chains" @@ -3387,7 +3387,7 @@ "href": "new_pages/phylogenetic_trees.html#preparation", "title": "38  Phylogenetic trees", "section": "38.2 Preparation", - "text": "38.2 Preparation\n\nLoad packages\nThis code chunk shows the loading of required packages. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # import/export\n here, # relative file paths\n tidyverse, # general data management and visualization\n ape, # to import and export phylogenetic files\n ggtree, # to visualize phylogenetic files\n treeio, # to visualize phylogenetic files\n ggnewscale) # to add additional layers of color schemes\n\n\n\nImport data\nThe data for this page can be downloaded with the instructions on the Download handbook and data page.\nThere are several different formats in which a phylogenetic tree can be stored (eg. Newick, NEXUS, Phylip). A common one is the Newick file format (.nwk), which is the standard for representing trees in computer-readable form. This means an entire tree can be expressed in a string format such as “((t2:0.04,t1:0.34):0.89,(t5:0.37,(t4:0.03,t3:0.67):0.9):0.59);”, listing all nodes and tips and their relationship (branch length) to each other.\nNote: It is important to understand that the phylogenetic tree file in itself does not contain sequencing data, but is merely the result of the genetic distances between the sequences. We therefore cannot extract sequencing data from a tree file.\nFirst, we use the read.tree() function from ape package to import a Newick phylogenetic tree file in .txt format, and store it in a list object of class “phylo”. If necessary, use the here() function from the here package to specify the relative file path.\nNote: In this case the newick tree is saved as a .txt file for easier handling and downloading from Github.\n\ntree <- ape::read.tree(\"Shigella_tree.txt\")\n\nWe inspect our tree object and see it contains 299 tips (or samples) and 236 nodes.\n\ntree\n\n\nPhylogenetic tree with 299 tips and 236 internal nodes.\n\nTip labels:\n SRR5006072, SRR4192106, S18BD07865, S18BD00489, S17BD08906, S17BD05939, ...\nNode labels:\n 17, 29, 100, 67, 100, 100, ...\n\nRooted; includes branch lengths.\n\n\nSecond, we import a table stored as a .csv file with additional information for each sequenced sample, such as gender, country of origin and attributes for antimicrobial resistance, using the import() function from the rio package:\n\nsample_data <- import(\"sample_data_Shigella_tree.csv\")\n\nBelow are the first 50 rows of the data:\n\n\n\n\n\n\n\n\nClean and inspect\nWe clean and inspect our data: In order to assign the correct sample data to the phylogenetic tree, the values in the column Sample_ID in the sample_data data frame need to match the tip.labels values in the tree file:\nWe check the formatting of the tip.labels in the tree file by looking at the first 6 entries using with head() from base R.\n\nhead(tree$tip.label) \n\n[1] \"SRR5006072\" \"SRR4192106\" \"S18BD07865\" \"S18BD00489\" \"S17BD08906\"\n[6] \"S17BD05939\"\n\n\nWe also make sure the first column in our sample_data data frame is Sample_ID. We look at the column names of our dataframe using colnames() from base R.\n\ncolnames(sample_data) \n\n [1] \"Sample_ID\" \"serotype\" \n [3] \"Country\" \"Continent\" \n [5] \"Travel_history\" \"Year\" \n [7] \"Belgium\" \"Source\" \n [9] \"Gender\" \"gyrA_mutations\" \n[11] \"macrolide_resistance_genes\" \"MIC_AZM\" \n[13] \"MIC_CIP\" \n\n\nWe look at the Sample_IDs in the data frame to make sure the formatting is the same than in the tip.label (eg. letters are all capitals, no extra underscores _ between letters and numbers, etc.)\n\nhead(sample_data$Sample_ID) # we again inspect only the first 6 using head()\n\n[1] \"S17BD05944\" \"S15BD07413\" \"S18BD07247\" \"S19BD07384\" \"S18BD07338\"\n[6] \"S18BD02657\"\n\n\nWe can also compare if all samples are present in the tree file and vice versa by generating a logical vector of TRUE or FALSE where they do or do not match. These are not printed here, for simplicity.\n\nsample_data$Sample_ID %in% tree$tip.label\n\ntree$tip.label %in% sample_data$Sample_ID\n\nWe can use these vectors to show any sample IDs that are not on the tree (there are none).\n\nsample_data$Sample_ID[!tree$tip.label %in% sample_data$Sample_ID]\n\ncharacter(0)\n\n\nUpon inspection we can see that the format of Sample_ID in the dataframe corresponds to the format of sample names at the tip.labels. These do not have to be sorted in the same order to be matched.\nWe are ready to go!", + "text": "38.2 Preparation\n\nLoad packages\nThis code chunk shows the loading of required packages. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\nHowever, the packages ggtree* and treeio** are not found on CRAN, and are found on an alternative package repository called Bioconductor, which is a collection of open source bioinformatics packages and software. To install these first, we need to use the package BiocManager.\n\n#Load and install BiocManager\npacman::p_load(\n BiocManager\n)\n\n#Install ggtree and treeio - note these are in quotation marks \" \"\nBiocManager::install(c(\n \"ggtree\",\n \"treeio\"\n))\n\nNow that those packages are installed, we can load them with pacman::p_load().\n\npacman::p_load(\n rio, # import/export\n here, # relative file paths\n tidyverse, # general data management and visualization\n ape, # to import and export phylogenetic files\n ggtree, # to visualize phylogenetic files\n treeio, # to visualize phylogenetic files\n tidytree, # to visualize phylogenetic files\n ggnewscale) # to add additional layers of color schemes\n\n\n\nImport data\nThe data for this page can be downloaded with the instructions on the Download handbook and data page.\nThere are several different formats in which a phylogenetic tree can be stored (eg. Newick, NEXUS, Phylip). A common one is the Newick file format (.nwk), which is the standard for representing trees in computer-readable form. This means an entire tree can be expressed in a string format such as \"((t2:0.04,t1:0.34):0.89,(t5:0.37, (t4:0.03,t3:0.67):0.9):0.59);\", listing all nodes and tips and their relationship (branch length) to each other.\nNote: It is important to understand that the phylogenetic tree file in itself does not contain sequencing data, but is merely the result of the genetic distances between the sequences. We therefore cannot extract sequencing data from a tree file.\nFirst, we use the read.tree() function from ape package to import a Newick phylogenetic tree file in .txt format, and store it in a list object of class “phylo”. If necessary, use the here() function from the here package to specify the relative file path.\nNote: In this case the newick tree is saved as a .txt file for easier handling and downloading from Github.\n\ntree <- ape::read.tree(\"Shigella_tree.txt\")\n\nWe inspect our tree object and see it contains 299 tips (or samples) and 236 nodes.\n\ntree\n\n\nPhylogenetic tree with 299 tips and 236 internal nodes.\n\nTip labels:\n SRR5006072, SRR4192106, S18BD07865, S18BD00489, S17BD08906, S17BD05939, ...\nNode labels:\n 17, 29, 100, 67, 100, 100, ...\n\nRooted; includes branch lengths.\n\n\nSecond, we import a table stored as a .csv file with additional information for each sequenced sample, such as gender, country of origin and attributes for antimicrobial resistance, using the import() function from the rio package:\n\nsample_data <- import(\"sample_data_Shigella_tree.csv\")\n\nBelow are the first 50 rows of the data:\n\n\n\n\n\n\n\n\nClean and inspect\nWe clean and inspect our data: In order to assign the correct sample data to the phylogenetic tree, the values in the column Sample_ID in the sample_data data frame need to match the tip.labels values in the tree file:\nWe check the formatting of the tip.labels in the tree file by looking at the first 6 entries using with head() from base R.\n\nhead(tree$tip.label) \n\n[1] \"SRR5006072\" \"SRR4192106\" \"S18BD07865\" \"S18BD00489\" \"S17BD08906\"\n[6] \"S17BD05939\"\n\n\nWe also make sure the first column in our sample_data data frame is Sample_ID. We look at the column names of our dataframe using colnames() from base R.\n\ncolnames(sample_data) \n\n [1] \"Sample_ID\" \"serotype\" \n [3] \"Country\" \"Continent\" \n [5] \"Travel_history\" \"Year\" \n [7] \"Belgium\" \"Source\" \n [9] \"Gender\" \"gyrA_mutations\" \n[11] \"macrolide_resistance_genes\" \"MIC_AZM\" \n[13] \"MIC_CIP\" \n\n\nWe look at the Sample_IDs in the data frame to make sure the formatting is the same than in the tip.label (eg. letters are all capitals, no extra underscores _ between letters and numbers, etc.)\n\nhead(sample_data$Sample_ID) # we again inspect only the first 6 using head()\n\n[1] \"S17BD05944\" \"S15BD07413\" \"S18BD07247\" \"S19BD07384\" \"S18BD07338\"\n[6] \"S18BD02657\"\n\n\nWe can also compare if all samples are present in the tree file and vice versa by generating a logical vector of TRUE or FALSE where they do or do not match. These are not printed here, for simplicity.\n\nsample_data$Sample_ID %in% tree$tip.label\n\ntree$tip.label %in% sample_data$Sample_ID\n\nWe can use these vectors to show any sample IDs that are not on the tree (there are none).\n\nsample_data$Sample_ID[!tree$tip.label %in% sample_data$Sample_ID]\n\ncharacter(0)\n\n\nUpon inspection we can see that the format of Sample_ID in the dataframe corresponds to the format of sample names at the tip.labels. These do not have to be sorted in the same order to be matched.\nWe are ready to go!", "crumbs": [ "Data Visualization", "38  Phylogenetic trees" @@ -3398,7 +3398,7 @@ "href": "new_pages/phylogenetic_trees.html#simple-tree-visualization", "title": "38  Phylogenetic trees", "section": "38.3 Simple tree visualization", - "text": "38.3 Simple tree visualization\n\nDifferent tree layouts\nggtree offers many different layout formats and some may be more suitable for your specific purpose than others. Below are a few demonstrations. For other options see this online book.\nHere are some example tree layouts:\n\nggtree(tree) # simple linear tree\nggtree(tree, branch.length = \"none\") # simple linear tree with all tips aligned\nggtree(tree, layout=\"circular\") # simple circular tree\nggtree(tree, layout=\"circular\", branch.length = \"none\") # simple circular tree with all tips aligned\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nSimple tree plus sample data\nThe %<+% operator is used to connect the sample_data data frame to the tree file. The most easy annotation of your tree is the addition of the sample names at the tips, as well as coloring of tip points and if desired the branches:\nHere is an example of a circular tree:\n\nggtree(tree, layout = \"circular\", branch.length = 'none') %<+% sample_data + # %<+% adds dataframe with sample data to tree\n aes(color = Belgium)+ # color the branches according to a variable in your dataframe\n scale_color_manual(\n name = \"Sample Origin\", # name of your color scheme (will show up in the legend like this)\n breaks = c(\"Yes\", \"No\"), # the different options in your variable\n labels = c(\"NRCSS Belgium\", \"Other\"), # how you want the different options named in your legend, allows for formatting\n values = c(\"blue\", \"black\"), # the color you want to assign to the variable \n na.value = \"black\") + # color NA values in black as well\n new_scale_color()+ # allows to add an additional color scheme for another variable\n geom_tippoint(\n mapping = aes(color = Continent), # tip color by continent. You may change shape adding \"shape = \"\n size = 1.5)+ # define the size of the point at the tip\n scale_color_brewer(\n name = \"Continent\", # name of your color scheme (will show up in the legend like this)\n palette = \"Set1\", # we choose a set of colors coming with the brewer package\n na.value = \"grey\") + # for the NA values we choose the color grey\n geom_tiplab( # adds name of sample to tip of its branch \n color = 'black', # (add as many text lines as you wish with + , but you may need to adjust offset value to place them next to each other)\n offset = 1,\n size = 1,\n geom = \"text\",\n align = TRUE)+ \n ggtitle(\"Phylogenetic tree of Shigella sonnei\")+ # title of your graph\n theme(\n axis.title.x = element_blank(), # removes x-axis title\n axis.title.y = element_blank(), # removes y-axis title\n legend.title = element_text( # defines font size and format of the legend title\n face = \"bold\",\n size = 12), \n legend.text=element_text( # defines font size and format of the legend text\n face = \"bold\",\n size = 10), \n plot.title = element_text( # defines font size and format of the plot title\n size = 12,\n face = \"bold\"), \n legend.position = \"bottom\", # defines placement of the legend\n legend.box = \"vertical\", # defines placement of the legend\n legend.margin = margin()) \n\n\n\n\n\n\n\n\nYou can export your tree plot with ggsave() as you would any other ggplot object. Written this way, ggsave() saves the last image produced to the file path you specify. Remember that you can use here() and relative file paths to easily save in subfolders, etc.\n\nggsave(\"example_tree_circular_1.png\", width = 12, height = 14)", + "text": "38.3 Simple tree visualization\n\nDifferent tree layouts\nggtree offers many different layout formats and some may be more suitable for your specific purpose than others. Below are a few demonstrations. For other options see this online book.\nHere are some example tree layouts:\n\nggtree(tree) # simple linear tree\nggtree(tree, branch.length = \"none\") # simple linear tree with all tips aligned\nggtree(tree, layout=\"circular\") # simple circular tree\nggtree(tree, layout=\"circular\", branch.length = \"none\") # simple circular tree with all tips aligned\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nSimple tree plus sample data\nThe %<+% operator is used to connect the sample_data data frame to the tree file. The most easy annotation of your tree is the addition of the sample names at the tips, as well as coloring of tip points and if desired the branches:\nHere is an example of a circular tree:\n\nggtree(tree, layout = \"circular\", branch.length = 'none') %<+% sample_data + # %<+% adds dataframe with sample data to tree\n aes(color = Belgium) + # color the branches according to a variable in your dataframe\n scale_color_manual(\n name = \"Sample Origin\", # name of your color scheme (will show up in the legend like this)\n breaks = c(\"Yes\", \"No\"), # the different options in your variable\n labels = c(\"NRCSS Belgium\", \"Other\"), # how you want the different options named in your legend, allows for formatting\n values = c(\"blue\", \"black\"), # the color you want to assign to the variable \n na.value = \"black\") + # color NA values in black as well\n new_scale_color() + # allows to add an additional color scheme for another variable\n geom_tippoint(\n mapping = aes(color = Continent), # tip color by continent. You may change shape adding \"shape = \"\n size = 1.5) + # define the size of the point at the tip\n scale_color_brewer(\n name = \"Continent\", # name of your color scheme (will show up in the legend like this)\n palette = \"Set1\", # we choose a set of colors coming with the brewer package\n na.value = \"grey\") + # for the NA values we choose the color grey\n geom_tiplab( # adds name of sample to tip of its branch \n color = 'black', # (add as many text lines as you wish with + , but you may need to adjust offset value to place them next to each other)\n offset = 1,\n size = 1,\n geom = \"text\",\n align = TRUE) + \n ggtitle(\"Phylogenetic tree of Shigella sonnei\") + # title of your graph\n theme(\n axis.title.x = element_blank(), # removes x-axis title\n axis.title.y = element_blank(), # removes y-axis title\n legend.title = element_text( # defines font size and format of the legend title\n face = \"bold\",\n size = 12), \n legend.text=element_text( # defines font size and format of the legend text\n face = \"bold\",\n size = 10), \n plot.title = element_text( # defines font size and format of the plot title\n size = 12,\n face = \"bold\"), \n legend.position = \"bottom\", # defines placement of the legend\n legend.box = \"vertical\", # defines placement of the legend\n legend.margin = margin()) \n\n\n\n\n\n\n\n\nYou can export your tree plot with ggsave() as you would any other ggplot object. Written this way, ggsave() saves the last image produced to the file path you specify. Remember that you can use here() and relative file paths to easily save in subfolders, etc.\n\nggsave(\"example_tree_circular_1.png\", width = 12, height = 14)", "crumbs": [ "Data Visualization", "38  Phylogenetic trees" @@ -3409,7 +3409,7 @@ "href": "new_pages/phylogenetic_trees.html#tree-manipulation", "title": "38  Phylogenetic trees", "section": "38.4 Tree manipulation", - "text": "38.4 Tree manipulation\nSometimes you may have a very large phylogenetic tree and you are only interested in one part of the tree. For example, if you produced a tree including historical or international samples to get a large overview of where your dataset might fit in the bigger picture. But then to look closer at your data you want to inspect only that portion of the bigger tree.\nSince the phylogenetic tree file is just the output of sequencing data analysis, we can not manipulate the order of the nodes and branches in the file itself. These have already been determined in previous analysis from the raw NGS data. We are able though to zoom into parts, hide parts and even subset part of the tree.\n\nZoom in\nIf you don’t want to “cut” your tree, but only inspect part of it more closely you can zoom in to view a specific part.\nFirst, we plot the entire tree in linear format and add numeric labels to each node in the tree.\n\np <- ggtree(tree,) %<+% sample_data +\n geom_tiplab(size = 1.5) + # labels the tips of all branches with the sample name in the tree file\n geom_text2(\n mapping = aes(subset = !isTip,\n label = node),\n size = 5,\n color = \"darkred\",\n hjust = 1,\n vjust = 1) # labels all the nodes in the tree\n\np # print\n\n\n\n\n\n\n\n\nTo zoom in to one particular branch (sticking out to the right), use viewClade() on the ggtree object p and provide the node number to get a closer look:\n\nviewClade(p, node = 452)\n\n\n\n\n\n\n\n\n\n\nCollapsing branches\nHowever, we may want to ignore this branch and can collapse it at that same node (node nr. 452) using collapse(). This tree is defined as p_collapsed.\n\np_collapsed <- collapse(p, node = 452)\np_collapsed\n\n\n\n\n\n\n\n\nFor clarity, when we print p_collapsed, we add a geom_point2() (a blue diamond) at the node of the collapsed branch.\n\np_collapsed + \ngeom_point2(aes(subset = (node == 452)), # we assign a symbol to the collapsed node\n size = 5, # define the size of the symbol\n shape = 23, # define the shape of the symbol\n fill = \"steelblue\") # define the color of the symbol\n\n\n\n\n\n\n\n\n\n\nSubsetting a tree\nIf we want to make a more permanent change and create a new, reduced tree to work with we can subset part of it with tree_subset(). Then you can save it as new newick tree file or .txt file.\nFirst, we inspect the tree nodes and tip labels in order to decide what to subset.\n\nggtree(\n tree,\n branch.length = 'none',\n layout = 'circular') %<+% sample_data + # we add the asmple data using the %<+% operator\n geom_tiplab(size = 1)+ # label tips of all branches with sample name in tree file\n geom_text2(\n mapping = aes(subset = !isTip, label = node),\n size = 3,\n color = \"darkred\") + # labels all the nodes in the tree\n theme(\n legend.position = \"none\", # removes the legend all together\n axis.title.x = element_blank(),\n axis.title.y = element_blank(),\n plot.title = element_text(size = 12, face=\"bold\"))\n\n\n\n\n\n\n\n\nNow, say we have decided to subset the tree at node 528 (keep only tips within this branch after node 528) and we save it as a new sub_tree1 object:\n\nsub_tree1 <- tree_subset(\n tree,\n node = 528) # we subset the tree at node 528\n\nLets have a look at the subset tree 1:\n\nggtree(sub_tree1) +\n geom_tiplab(size = 3) +\n ggtitle(\"Subset tree 1\")\n\n\n\n\n\n\n\n\nYou can also subset based on one particular sample, specifying how many nodes “backwards” you want to include. Let’s subset the same part of the tree based on a sample, in this case S17BD07692, going back 9 nodes and we save it as a new sub_tree2 object:\n\nsub_tree2 <- tree_subset(\n tree,\n \"S17BD07692\",\n levels_back = 9) # levels back defines how many nodes backwards from the sample tip you want to go\n\nLets have a look at the subset tree 2:\n\nggtree(sub_tree2) +\n geom_tiplab(size =3) +\n ggtitle(\"Subset tree 2\")\n\n\n\n\n\n\n\n\nYou can also save your new tree either as a Newick type or even a text file using the write.tree() function from ape package:\n\n# to save in .nwk format\nape::write.tree(sub_tree2, file='data/phylo/Shigella_subtree_2.nwk')\n\n# to save in .txt format\nape::write.tree(sub_tree2, file='data/phylo/Shigella_subtree_2.txt')\n\n\n\nRotating nodes in a tree\nAs mentioned before we cannot change the order of tips or nodes in the tree, as this is based on their genetic relatedness and is not subject to visual manipulation. But we can rote branches around nodes if that eases our visualization.\nFirst, we plot our new subset tree 2 with node labels to choose the node we want to manipulate and store it an a ggtree plot object p.\n\np <- ggtree(sub_tree2) + \n geom_tiplab(size = 4) +\n geom_text2(aes(subset=!isTip, label=node), # labels all the nodes in the tree\n size = 5,\n color = \"darkred\", \n hjust = 1, \n vjust = 1) \np\n\n\n\n\n\n\n\n\nWe can then manipulate nodes by applying ggtree::rotate() or ggtree::flip(): Note: to illustrate which nodes we are manipulating we first apply the geom_hilight() function from ggtree to highlight the samples in the nodes we are interested in and store that ggtree plot object in a new object p1.\n\np1 <- p + geom_hilight( # highlights node 39 in blue, \"extend =\" allows us to define the length of the color block\n node = 39,\n fill = \"steelblue\",\n extend = 0.0017) + \ngeom_hilight( # highlights the node 37 in yellow\n node = 37,\n fill = \"yellow\",\n extend = 0.0017) + \nggtitle(\"Original tree\")\n\n\np1 # print\n\n\n\n\n\n\n\n\nNow we can rotate node 37 in object p1 so that the samples on node 38 move to the top. We store the rotated tree in a new object p2.\n\np2 <- ggtree::rotate(p1, 37) + \n ggtitle(\"Rotated Node 37\")\n\n\np2 # print\n\n\n\n\n\n\n\n\nOr we can use the flip command to rotate node 36 in object p1 and switch node 37 to the top and node 39 to the bottom. We store the flipped tree in a new object p3.\n\np3 <- flip(p1, 39, 37) +\n ggtitle(\"Rotated Node 36\")\n\n\np3 # print\n\n\n\n\n\n\n\n\n\n\nExample subtree with sample data annotation\nLets say we are investigating the cluster of cases with clonal expansion which occurred in 2017 and 2018 at node 39 in our sub-tree. We add the year of strain isolation as well as travel history and color by country to see origin of other closely related strains:\n\nggtree(sub_tree2) %<+% sample_data + # we use th %<+% operator to link to the sample_data\n geom_tiplab( # labels the tips of all branches with the sample name in the tree file\n size = 2.5,\n offset = 0.001,\n align = TRUE) + \n theme_tree2()+\n xlim(0, 0.015)+ # set the x-axis limits of our tree\n geom_tippoint(aes(color=Country), # color the tip point by continent\n size = 1.5)+ \n scale_color_brewer(\n name = \"Country\", \n palette = \"Set1\", \n na.value = \"grey\")+\n geom_tiplab( # add isolation year as a text label at the tips\n aes(label = Year),\n color = 'blue',\n offset = 0.0045,\n size = 3,\n linetype = \"blank\" ,\n geom = \"text\",\n align = TRUE)+ \n geom_tiplab( # add travel history as a text label at the tips, in red color\n aes(label = Travel_history),\n color = 'red',\n offset = 0.006,\n size = 3,\n linetype = \"blank\",\n geom = \"text\",\n align = TRUE)+ \n ggtitle(\"Phylogenetic tree of Belgian S. sonnei strains with travel history\")+ # add plot title\n xlab(\"genetic distance (0.001 = 4 nucleotides difference)\")+ # add a label to the x-axis \n theme(\n axis.title.x = element_text(size = 10),\n axis.title.y = element_blank(),\n legend.title = element_text(face = \"bold\", size = 12),\n legend.text = element_text(face = \"bold\", size = 10),\n plot.title = element_text(size = 12, face = \"bold\"))\n\n\n\n\n\n\n\n\nOur observation points towards an import event of strains from Asia, which then circulated in Belgium over the years and seem to have caused our latest outbreak.", + "text": "38.4 Tree manipulation\nSometimes you may have a very large phylogenetic tree and you are only interested in one part of the tree. For example, if you produced a tree including historical or international samples to get a large overview of where your dataset might fit in the bigger picture. But then to look closer at your data you want to inspect only that portion of the bigger tree.\nSince the phylogenetic tree file is just the output of sequencing data analysis, we can not manipulate the order of the nodes and branches in the file itself. These have already been determined in previous analysis from the raw NGS data. We are able though to zoom into parts, hide parts and even subset part of the tree.\n\nZoom in\nIf you don’t want to “cut” your tree, but only inspect part of it more closely you can zoom in to view a specific part.\nFirst, we plot the entire tree in linear format and add numeric labels to each node in the tree.\n\np <- ggtree(tree,) %<+% sample_data +\n geom_tiplab(size = 1.5) + # labels the tips of all branches with the sample name in the tree file\n geom_text2(\n mapping = aes(subset = !isTip,\n label = node),\n size = 5,\n color = \"darkred\",\n hjust = 1,\n vjust = 1) # labels all the nodes in the tree\n\np # print\n\n\n\n\n\n\n\n\nTo zoom in to one particular branch (sticking out to the right), use viewClade() on the ggtree object p and provide the node number to get a closer look:\n\nviewClade(p, node = 452)\n\n\n\n\n\n\n\n\n\n\nCollapsing branches\nHowever, we may want to ignore this branch and can collapse it at that same node (node nr. 452) using collapse(). This tree is defined as p_collapsed.\n\np_collapsed <- collapse(p, node = 452)\np_collapsed\n\n\n\n\n\n\n\n\nFor clarity, when we print p_collapsed, we add a geom_point2() (a blue diamond) at the node of the collapsed branch.\n\np_collapsed + \ngeom_point2(aes(subset = (node == 452)), # we assign a symbol to the collapsed node\n size = 5, # define the size of the symbol\n shape = 23, # define the shape of the symbol\n fill = \"steelblue\") # define the color of the symbol\n\n\n\n\n\n\n\n\n\n\nSubsetting a tree\nIf we want to make a more permanent change and create a new, reduced tree to work with we can subset part of it with tree_subset(). Then you can save it as new newick tree file or .txt file.\nFirst, we inspect the tree nodes and tip labels in order to decide what to subset.\n\nggtree(\n tree,\n branch.length = 'none',\n layout = 'circular') %<+% sample_data + # we add the asmple data using the %<+% operator\n geom_tiplab(size = 1) + # label tips of all branches with sample name in tree file\n geom_text2(\n mapping = aes(subset = !isTip, label = node),\n size = 3,\n color = \"darkred\") + # labels all the nodes in the tree\n theme(\n legend.position = \"none\", # removes the legend all together\n axis.title.x = element_blank(),\n axis.title.y = element_blank(),\n plot.title = element_text(size = 12, face=\"bold\"))\n\n\n\n\n\n\n\n\nNow, say we have decided to subset the tree at node 528 (keep only tips within this branch after node 528) and we save it as a new sub_tree1 object:\n\nsub_tree1 <- tree_subset(\n tree,\n node = 528) # we subset the tree at node 528\n\nLets have a look at the subset tree 1:\n\nggtree(sub_tree1) +\n geom_tiplab(size = 3) +\n ggtitle(\"Subset tree 1\")\n\n\n\n\n\n\n\n\nYou can also subset based on one particular sample, specifying how many nodes “backwards” you want to include. Let’s subset the same part of the tree based on a sample, in this case S17BD07692, going back 9 nodes and we save it as a new sub_tree2 object:\n\nsub_tree2 <- tree_subset(\n tree,\n \"S17BD07692\",\n levels_back = 9) # levels back defines how many nodes backwards from the sample tip you want to go\n\nLets have a look at the subset tree 2:\n\nggtree(sub_tree2) +\n geom_tiplab(size = 3) +\n ggtitle(\"Subset tree 2\")\n\n\n\n\n\n\n\n\nYou can also save your new tree either as a Newick type or even a text file using the write.tree() function from ape package:\n\n# to save in .nwk format\nape::write.tree(sub_tree2, file='data/phylo/Shigella_subtree_2.nwk')\n\n# to save in .txt format\nape::write.tree(sub_tree2, file='data/phylo/Shigella_subtree_2.txt')\n\n\n\nRotating nodes in a tree\nAs mentioned before we cannot change the order of tips or nodes in the tree, as this is based on their genetic relatedness and is not subject to visual manipulation. But we can rote branches around nodes if that eases our visualization.\nFirst, we plot our new subset tree 2 with node labels to choose the node we want to manipulate and store it an a ggtree plot object p.\n\np <- ggtree(sub_tree2) + \n geom_tiplab(size = 4) +\n geom_text2(aes(subset=!isTip, label=node), # labels all the nodes in the tree\n size = 5,\n color = \"darkred\", \n hjust = 1, \n vjust = 1) \np\n\n\n\n\n\n\n\n\nWe can then manipulate nodes by applying ggtree::rotate() or ggtree::flip(): Note: to illustrate which nodes we are manipulating we first apply the geom_hilight() function from ggtree to highlight the samples in the nodes we are interested in and store that ggtree plot object in a new object p1.\n\np1 <- p + geom_hilight( # highlights node 39 in blue, \"extend =\" allows us to define the length of the color block\n node = 39,\n fill = \"steelblue\",\n extend = 0.0017) + \ngeom_hilight( # highlights the node 37 in yellow\n node = 37,\n fill = \"yellow\",\n extend = 0.0017) + \nggtitle(\"Original tree\")\n\n\np1 # print\n\n\n\n\n\n\n\n\nNow we can rotate node 37 in object p1 so that the samples on node 38 move to the top. We store the rotated tree in a new object p2.\n\np2 <- ggtree::rotate(p1, 37) + \n ggtitle(\"Rotated Node 37\")\n\n\np2 # print\n\n\n\n\n\n\n\n\nOr we can use the flip command to rotate node 36 in object p1 and switch node 37 to the top and node 39 to the bottom. We store the flipped tree in a new object p3.\n\np3 <- flip(p1, 39, 37) +\n ggtitle(\"Rotated Node 36\")\n\n\np3 # print\n\n\n\n\n\n\n\n\n\n\nExample subtree with sample data annotation\nLets say we are investigating the cluster of cases with clonal expansion which occurred in 2017 and 2018 at node 39 in our sub-tree. We add the year of strain isolation as well as travel history and color by country to see origin of other closely related strains:\n\nggtree(sub_tree2) %<+% sample_data + # we use th %<+% operator to link to the sample_data\n geom_tiplab( # labels the tips of all branches with the sample name in the tree file\n size = 2.5,\n offset = 0.001,\n align = TRUE) + \n theme_tree2() +\n xlim(0, 0.015) + # set the x-axis limits of our tree\n geom_tippoint(aes(color=Country), # color the tip point by continent\n size = 1.5) + \n scale_color_brewer(\n name = \"Country\", \n palette = \"Set1\", \n na.value = \"grey\") +\n geom_tiplab( # add isolation year as a text label at the tips\n aes(label = Year),\n color = 'blue',\n offset = 0.0045,\n size = 3,\n linetype = \"blank\" ,\n geom = \"text\",\n align = TRUE) + \n geom_tiplab( # add travel history as a text label at the tips, in red color\n aes(label = Travel_history),\n color = 'red',\n offset = 0.006,\n size = 3,\n linetype = \"blank\",\n geom = \"text\",\n align = TRUE) + \n ggtitle(\"Phylogenetic tree of Belgian S. sonnei strains with travel history\") + # add plot title\n xlab(\"genetic distance (0.001 = 4 nucleotides difference)\") + # add a label to the x-axis \n theme(\n axis.title.x = element_text(size = 10),\n axis.title.y = element_blank(),\n legend.title = element_text(face = \"bold\", size = 12),\n legend.text = element_text(face = \"bold\", size = 10),\n plot.title = element_text(size = 12, face = \"bold\"))\n\n\n\n\n\n\n\n\nOur observation points towards an import event of strains from Asia, which then circulated in Belgium over the years and seem to have caused our latest outbreak.", "crumbs": [ "Data Visualization", "38  Phylogenetic trees" @@ -3420,7 +3420,7 @@ "href": "new_pages/phylogenetic_trees.html#more-complex-trees-adding-heatmaps-of-sample-data", "title": "38  Phylogenetic trees", "section": "More complex trees: adding heatmaps of sample data", - "text": "More complex trees: adding heatmaps of sample data\nWe can add more complex information, such as categorical presence of antimicrobial resistance genes and numeric values for actually measured resistance to antimicrobials in form of a heatmap using the ggtree::gheatmap() function.\nFirst we need to plot our tree (this can be either linear or circular) and store it in a new ggtree plot object p: We will use the sub_tree from part 3.)\n\np <- ggtree(sub_tree2, branch.length='none', layout='circular') %<+% sample_data +\n geom_tiplab(size =3) + \n theme(\n legend.position = \"none\",\n axis.title.x = element_blank(),\n axis.title.y = element_blank(),\n plot.title = element_text(\n size = 12,\n face = \"bold\",\n hjust = 0.5,\n vjust = -15))\np\n\n\n\n\n\n\n\n\nSecond, we prepare our data. To visualize different variables with new color schemes, we subset our dataframe to the desired variable. It is important to add the Sample_ID as rownames otherwise it cannot match the data to the tree tip.labels:\nIn our example we want to look at gender and mutations that could confer resistance to Ciprofloxacin, an important first line antibiotic used to treat Shigella infections.\nWe create a dataframe for gender:\n\ngender <- data.frame(\"gender\" = sample_data[,c(\"Gender\")])\nrownames(gender) <- sample_data$Sample_ID\n\nWe create a dataframe for mutations in the gyrA gene, which confer Ciprofloxacin resistance:\n\ncipR <- data.frame(\"cipR\" = sample_data[,c(\"gyrA_mutations\")])\nrownames(cipR) <- sample_data$Sample_ID\n\nWe create a dataframe for the measured minimum inhibitory concentration (MIC) for Ciprofloxacin from the laboratory:\n\nMIC_Cip <- data.frame(\"mic_cip\" = sample_data[,c(\"MIC_CIP\")])\nrownames(MIC_Cip) <- sample_data$Sample_ID\n\nWe create a first plot adding a binary heatmap for gender to the phylogenetic tree and storing it in a new ggtree plot object h1:\n\nh1 <- gheatmap(p, gender, # we add a heatmap layer of the gender dataframe to our tree plot\n offset = 10, # offset shifts the heatmap to the right,\n width = 0.10, # width defines the width of the heatmap column,\n color = NULL, # color defines the boarder of the heatmap columns\n colnames = FALSE) + # hides column names for the heatmap\n scale_fill_manual(name = \"Gender\", # define the coloring scheme and legend for gender\n values = c(\"#00d1b1\", \"purple\"),\n breaks = c(\"Male\", \"Female\"),\n labels = c(\"Male\", \"Female\")) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh1\n\n\n\n\n\n\n\n\nThen we add information on mutations in the gyrA gene, which confer resistance to Ciprofloxacin:\nNote: The presence of chromosomal point mutations in WGS data was prior determined using the PointFinder tool developed by Zankari et al. (see reference in the additional references section)\nFirst, we assign a new color scheme to our existing plot object h1 and store it in a now object h2. This enables us to define and change the colors for our second variable in the heatmap.\n\nh2 <- h1 + new_scale_fill() \n\nThen we add the second heatmap layer to h2 and store the combined plots in a new object h3:\n\nh3 <- gheatmap(h2, cipR, # adds the second row of heatmap describing Ciprofloxacin resistance mutations\n offset = 12, \n width = 0.10, \n colnames = FALSE) +\n scale_fill_manual(name = \"Ciprofloxacin resistance \\n conferring mutation\",\n values = c(\"#fe9698\",\"#ea0c92\"),\n breaks = c( \"gyrA D87Y\", \"gyrA S83L\"),\n labels = c( \"gyrA d87y\", \"gyrA s83l\")) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())+\n guides(fill = guide_legend(nrow = 2,byrow = TRUE))\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh3\n\n\n\n\n\n\n\n\nWe repeat the above process, by first adding a new color scale layer to our existing object h3, and then adding the continuous data on the minimum inhibitory concentration (MIC) of Ciprofloxacin for each strain to the resulting object h4 to produce the final object h5:\n\n# First we add the new coloring scheme:\nh4 <- h3 + new_scale_fill()\n\n# then we combine the two into a new plot:\nh5 <- gheatmap(h4, MIC_Cip, \n offset = 14, \n width = 0.10,\n colnames = FALSE)+\n scale_fill_continuous(name = \"MIC for Ciprofloxacin\", # here we define a gradient color scheme for the continuous variable of MIC\n low = \"yellow\", high = \"red\",\n breaks = c(0, 0.50, 1.00),\n na.value = \"white\") +\n guides(fill = guide_colourbar(barwidth = 5, barheight = 1))+\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh5\n\n\n\n\n\n\n\n\nWe can do the same exercise for a linear tree:\n\np <- ggtree(sub_tree2) %<+% sample_data +\n geom_tiplab(size = 3) + # labels the tips\n theme_tree2()+\n xlab(\"genetic distance (0.001 = 4 nucleotides difference)\")+\n xlim(0, 0.015)+\n theme(legend.position = \"none\",\n axis.title.y = element_blank(),\n plot.title = element_text(size = 12, \n face = \"bold\",\n hjust = 0.5,\n vjust = -15))\np\n\n\n\n\n\n\n\n\nFirst we add gender:\n\nh1 <- gheatmap(p, gender, \n offset = 0.003,\n width = 0.1, \n color=\"black\", \n colnames = FALSE)+\n scale_fill_manual(name = \"Gender\",\n values = c(\"#00d1b1\", \"purple\"),\n breaks = c(\"Male\", \"Female\"),\n labels = c(\"Male\", \"Female\"))+\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh1\n\n\n\n\n\n\n\n\nThen we add Ciprofloxacin resistance mutations after adding another color scheme layer:\n\nh2 <- h1 + new_scale_fill()\nh3 <- gheatmap(h2, cipR, \n offset = 0.004, \n width = 0.1,\n color = \"black\",\n colnames = FALSE)+\n scale_fill_manual(name = \"Ciprofloxacin resistance \\n conferring mutation\",\n values = c(\"#fe9698\",\"#ea0c92\"),\n breaks = c( \"gyrA D87Y\", \"gyrA S83L\"),\n labels = c( \"gyrA d87y\", \"gyrA s83l\"))+\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())+\n guides(fill = guide_legend(nrow = 2,byrow = TRUE))\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\n h3\n\n\n\n\n\n\n\n\nThen we add the minimum inhibitory concentration determined by the laboratory (MIC):\n\nh4 <- h3 + new_scale_fill()\nh5 <- gheatmap(h4, MIC_Cip, \n offset = 0.005, \n width = 0.1,\n color = \"black\", \n colnames = FALSE)+\n scale_fill_continuous(name = \"MIC for Ciprofloxacin\",\n low = \"yellow\", high = \"red\",\n breaks = c(0,0.50,1.00),\n na.value = \"white\")+\n guides(fill = guide_colourbar(barwidth = 5, barheight = 1))+\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 10),\n legend.text = element_text(size = 8),\n legend.box = \"horizontal\", legend.margin = margin())+\n guides(shape = guide_legend(override.aes = list(size = 2)))\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh5", + "text": "More complex trees: adding heatmaps of sample data\nWe can add more complex information, such as categorical presence of antimicrobial resistance genes and numeric values for actually measured resistance to antimicrobials in form of a heatmap using the ggtree::gheatmap() function.\nFirst we need to plot our tree (this can be either linear or circular) and store it in a new ggtree plot object p: We will use the sub_tree from part 3.)\n\np <- ggtree(sub_tree2, branch.length = 'none', layout = 'circular') %<+% sample_data +\n geom_tiplab(size = 3) + \n theme(\n legend.position = \"none\",\n axis.title.x = element_blank(),\n axis.title.y = element_blank(),\n plot.title = element_text(\n size = 12,\n face = \"bold\",\n hjust = 0.5,\n vjust = -15))\np\n\n\n\n\n\n\n\n\nSecond, we prepare our data. To visualize different variables with new color schemes, we subset our dataframe to the desired variable. It is important to add the Sample_ID as rownames otherwise it cannot match the data to the tree tip.labels:\nIn our example we want to look at gender and mutations that could confer resistance to Ciprofloxacin, an important first line antibiotic used to treat Shigella infections.\nWe create a dataframe for gender:\n\ngender <- data.frame(\"gender\" = sample_data[,c(\"Gender\")])\nrownames(gender) <- sample_data$Sample_ID\n\nWe create a dataframe for mutations in the gyrA gene, which confer Ciprofloxacin resistance:\n\ncipR <- data.frame(\"cipR\" = sample_data[,c(\"gyrA_mutations\")])\nrownames(cipR) <- sample_data$Sample_ID\n\nWe create a dataframe for the measured minimum inhibitory concentration (MIC) for Ciprofloxacin from the laboratory:\n\nMIC_Cip <- data.frame(\"mic_cip\" = sample_data[,c(\"MIC_CIP\")])\nrownames(MIC_Cip) <- sample_data$Sample_ID\n\nWe create a first plot adding a binary heatmap for gender to the phylogenetic tree and storing it in a new ggtree plot object h1:\n\nh1 <- gheatmap(p, gender, # we add a heatmap layer of the gender dataframe to our tree plot\n offset = 10, # offset shifts the heatmap to the right,\n width = 0.10, # width defines the width of the heatmap column,\n color = NULL, # color defines the boarder of the heatmap columns\n colnames = FALSE) + # hides column names for the heatmap\n scale_fill_manual(name = \"Gender\", # define the coloring scheme and legend for gender\n values = c(\"#00d1b1\", \"purple\"),\n breaks = c(\"Male\", \"Female\"),\n labels = c(\"Male\", \"Female\")) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh1\n\n\n\n\n\n\n\n\nThen we add information on mutations in the gyrA gene, which confer resistance to Ciprofloxacin:\nNote: The presence of chromosomal point mutations in WGS data was prior determined using the PointFinder tool developed by Zankari et al. (see reference in the additional references section)\nFirst, we assign a new color scheme to our existing plot object h1 and store it in a now object h2. This enables us to define and change the colors for our second variable in the heatmap.\n\nh2 <- h1 + new_scale_fill() \n\nThen we add the second heatmap layer to h2 and store the combined plots in a new object h3:\n\nh3 <- gheatmap(h2, cipR, # adds the second row of heatmap describing Ciprofloxacin resistance mutations\n offset = 12, \n width = 0.10, \n colnames = FALSE) +\n scale_fill_manual(name = \"Ciprofloxacin resistance \\n conferring mutation\",\n values = c(\"#fe9698\",\"#ea0c92\"),\n breaks = c( \"gyrA D87Y\", \"gyrA S83L\"),\n labels = c( \"gyrA d87y\", \"gyrA s83l\")) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin()) +\n guides(fill = guide_legend(nrow = 2,byrow = TRUE))\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh3\n\n\n\n\n\n\n\n\nWe repeat the above process, by first adding a new color scale layer to our existing object h3, and then adding the continuous data on the minimum inhibitory concentration (MIC) of Ciprofloxacin for each strain to the resulting object h4 to produce the final object h5:\n\n# First we add the new coloring scheme:\nh4 <- h3 + new_scale_fill()\n\n# then we combine the two into a new plot:\nh5 <- gheatmap(h4, MIC_Cip, \n offset = 14, \n width = 0.10,\n colnames = FALSE) +\n scale_fill_continuous(name = \"MIC for Ciprofloxacin\", # here we define a gradient color scheme for the continuous variable of MIC\n low = \"yellow\", high = \"red\",\n breaks = c(0, 0.50, 1.00),\n na.value = \"white\") +\n guides(fill = guide_colourbar(barwidth = 5, barheight = 1)) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh5\n\n\n\n\n\n\n\n\nWe can do the same exercise for a linear tree:\n\np <- ggtree(sub_tree2) %<+% sample_data +\n geom_tiplab(size = 3) + # labels the tips\n theme_tree2() +\n xlab(\"genetic distance (0.001 = 4 nucleotides difference)\") +\n xlim(0, 0.015) +\n theme(legend.position = \"none\",\n axis.title.y = element_blank(),\n plot.title = element_text(size = 12, \n face = \"bold\",\n hjust = 0.5,\n vjust = -15))\np\n\n\n\n\n\n\n\n\nFirst we add gender:\n\nh1 <- gheatmap(p, gender, \n offset = 0.003,\n width = 0.1, \n color=\"black\", \n colnames = FALSE) +\n scale_fill_manual(name = \"Gender\",\n values = c(\"#00d1b1\", \"purple\"),\n breaks = c(\"Male\", \"Female\"),\n labels = c(\"Male\", \"Female\")) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin())\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh1\n\n\n\n\n\n\n\n\nThen we add Ciprofloxacin resistance mutations after adding another color scheme layer:\n\nh2 <- h1 + new_scale_fill()\nh3 <- gheatmap(h2, cipR, \n offset = 0.004, \n width = 0.1,\n color = \"black\",\n colnames = FALSE) +\n scale_fill_manual(name = \"Ciprofloxacin resistance \\n conferring mutation\",\n values = c(\"#fe9698\",\"#ea0c92\"),\n breaks = c( \"gyrA D87Y\", \"gyrA S83L\"),\n labels = c( \"gyrA d87y\", \"gyrA s83l\")) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 12),\n legend.text = element_text(size = 10),\n legend.box = \"vertical\", legend.margin = margin()) +\n guides(fill = guide_legend(nrow = 2,byrow = TRUE))\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\n h3\n\n\n\n\n\n\n\n\nThen we add the minimum inhibitory concentration determined by the laboratory (MIC):\n\nh4 <- h3 + new_scale_fill()\nh5 <- gheatmap(h4, MIC_Cip, \n offset = 0.005, \n width = 0.1,\n color = \"black\", \n colnames = FALSE) +\n scale_fill_continuous(name = \"MIC for Ciprofloxacin\",\n low = \"yellow\", high = \"red\",\n breaks = c(0,0.50,1.00),\n na.value = \"white\") +\n guides(fill = guide_colourbar(barwidth = 5, barheight = 1)) +\n theme(legend.position = \"bottom\",\n legend.title = element_text(size = 10),\n legend.text = element_text(size = 8),\n legend.box = \"horizontal\", legend.margin = margin()) +\n guides(shape = guide_legend(override.aes = list(size = 2)))\n\nScale for y is already present.\nAdding another scale for y, which will replace the existing scale.\nScale for fill is already present.\nAdding another scale for fill, which will replace the existing scale.\n\nh5", "crumbs": [ "Data Visualization", "38  Phylogenetic trees" @@ -3431,7 +3431,7 @@ "href": "new_pages/phylogenetic_trees.html#resources", "title": "38  Phylogenetic trees", "section": "38.5 Resources", - "text": "38.5 Resources\nhttp://hydrodictyon.eeb.uconn.edu/eebedia/index.php/Ggtree# Clade_Colors https://bioconductor.riken.jp/packages/3.2/bioc/vignettes/ggtree/inst/doc/treeManipulation.html https://guangchuangyu.github.io/ggtree-book/chapter-ggtree.html https://bioconductor.riken.jp/packages/3.8/bioc/vignettes/ggtree/inst/doc/treeManipulation.html\nEa Zankari, Rosa Allesøe, Katrine G Joensen, Lina M Cavaco, Ole Lund, Frank M Aarestrup, PointFinder: a novel web tool for WGS-based detection of antimicrobial resistance associated with chromosomal point mutations in bacterial pathogens, Journal of Antimicrobial Chemotherapy, Volume 72, Issue 10, October 2017, Pages 2764–2768, https://doi.org/10.1093/jac/dkx217", + "text": "38.5 Resources\nggtree Tree manipulation in ggtree Visualisation and annotation of phylogenetic trees in ggtree PointFinder: a novel web tool for WGS-based detection of antimicrobial resistance associated with chromosomal point mutations in bacterial pathogens", "crumbs": [ "Data Visualization", "38  Phylogenetic trees" @@ -3464,7 +3464,7 @@ "href": "new_pages/interactive_plots.html#plot-with-ggplotly", "title": "39  Interactive plots", "section": "39.2 Plot with ggplotly()", - "text": "39.2 Plot with ggplotly()\nThe function ggplotly() from the plotly package makes it easy to convert a ggplot() to be interactive. Simply save your ggplot() and then pipe it to the ggplotly() function.\nBelow, we plot a simple line representing the proportion of cases who died in a given week:\nWe begin by creating a summary dataset of each epidemiological week, and the percent of cases with a known outcome that died.\n\nweekly_deaths <- linelist %>%\n group_by(epiweek = floor_date(date_onset, \"week\")) %>% # create and group data by epiweek column\n summarise( # create new summary data frame:\n n_known_outcome = sum(!is.na(outcome), na.rm=T), # number of cases per group with known outcome\n n_death = sum(outcome == \"Death\", na.rm=T), # number of cases per group who died\n pct_death = 100*(n_death / n_known_outcome) # percent of cases with known outcome who died\n )\n\nHere is the first 50 rows of the weekly_deaths dataset.\n\n\n\n\n\n\nThen we create the plot with ggplot2, using geom_line().\n\ndeaths_plot <- ggplot(data = weekly_deaths)+ # begin with weekly deaths data\n geom_line(mapping = aes(x = epiweek, y = pct_death)) # make line \n\ndeaths_plot # print\n\n\n\n\n\n\n\n\nWe can make this interactive by simply passing this plot to ggplotly(), as below. Hover your mouse over the line to show the x and y values. You can zoom in on the plot, and drag it around. You can also see icons in the upper-right of the plot. In order, they allow you to:\n\nDownload the current view as a PNG image\n\nZoom in with a select box\n\n“Pan”, or move across the plot by clicking and dragging the plot\n\nZoom in, zoom out, or return to default zoom\n\nReset axes to defaults\n\nToggle on/off “spike lines” which are dotted lines from the interactive point extending to the x and y axes\n\nAdjustments to whether data show when you are not hovering on the line\n\n\ndeaths_plot %>% plotly::ggplotly()\n\n\n\n\n\nGrouped data work with ggplotly() as well. Below, a weekly epicurve is made, grouped by outcome. The stacked bars are interactive. Try clicking on the different items in the legend (they will appear/disappear).\n\n# Make epidemic curve with incidence2 pacakge\np <- incidence2::incidence(\n linelist,\n date_index = date_onset,\n interval = \"weeks\",\n groups = outcome) %>% plot(fill = outcome)\n\n\n# Plot interactively \np %>% plotly::ggplotly()", + "text": "39.2 Plot with ggplotly()\nThe function ggplotly() from the plotly package makes it easy to convert a ggplot() to be interactive. Simply save your ggplot() and then pipe it to the ggplotly() function.\nBelow, we plot a simple line representing the proportion of cases who died in a given week:\nWe begin by creating a summary dataset of each epidemiological week, and the percent of cases with a known outcome that died.\n\nweekly_deaths <- linelist %>%\n group_by(epiweek = floor_date(date_onset, \"week\")) %>% # create and group data by epiweek column\n summarise( # create new summary data frame:\n n_known_outcome = sum(!is.na(outcome), na.rm=T), # number of cases per group with known outcome\n n_death = sum(outcome == \"Death\", na.rm=T), # number of cases per group who died\n pct_death = 100*(n_death / n_known_outcome) # percent of cases with known outcome who died\n )\n\nHere is the first 50 rows of the weekly_deaths dataset.\n\n\n\n\n\n\nThen we create the plot with ggplot2, using geom_line().\n\ndeaths_plot <- ggplot(data = weekly_deaths) + # begin with weekly deaths data\n geom_line(mapping = aes(x = epiweek, y = pct_death)) # make line \n\ndeaths_plot # print\n\n\n\n\n\n\n\n\nWe can make this interactive by simply passing this plot to ggplotly(), as below. Hover your mouse over the line to show the x and y values. You can zoom in on the plot, and drag it around. You can also see icons in the upper-right of the plot. In order, they allow you to:\n\nDownload the current view as a PNG image.\n\nZoom in with a select box.\n\n“Pan”, or move across the plot by clicking and dragging the plot.\n\nZoom in, zoom out, or return to default zoom.\n\nReset axes to defaults.\n\nToggle on/off “spike lines” which are dotted lines from the interactive point extending to the x and y axes.\n\nAdjustments to whether data show when you are not hovering on the line.\n\n\ndeaths_plot %>% \n plotly::ggplotly()\n\n\n\n\n\nGrouped data work with ggplotly() as well. Below, a weekly epicurve is made, grouped by outcome. The stacked bars are interactive. Try clicking on the different items in the legend (they will appear/disappear).\n\n# Make epidemic curve with incidence2 package\np <- incidence2::incidence(\n linelist,\n date_index = \"date_onset\",\n interval = \"weeks\",\n groups = \"outcome\") %>% \n plot(fill = \"outcome\")\n\n\n# Plot interactively \np %>% \n plotly::ggplotly()", "crumbs": [ "Data Visualization", "39  Interactive plots" @@ -3486,7 +3486,7 @@ "href": "new_pages/interactive_plots.html#heat-tiles", "title": "39  Interactive plots", "section": "39.4 Heat tiles", - "text": "39.4 Heat tiles\nYou can make almost any ggplot() plot interactive, including heat tiles. In the page on Heat plots you can read about how to make the below plot, which displays the proportion of days per week that certain facilities reported data to their province.\nHere is the code, although we will not describe it in depth here.\n\n# import data\nfacility_count_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\"))\n\n# aggregate data into Weeks for Spring district\nagg_weeks <- facility_count_data %>% \n filter(District == \"Spring\",\n data_date < as.Date(\"2020-08-01\")) %>% \n mutate(week = aweek::date2week(\n data_date,\n start_date = \"Monday\",\n floor_day = TRUE,\n factor = TRUE)) %>% \n group_by(location_name, week, .drop = F) %>%\n summarise(\n n_days = 7,\n n_reports = n(),\n malaria_tot = sum(malaria_tot, na.rm = T),\n n_days_reported = length(unique(data_date)),\n p_days_reported = round(100*(n_days_reported / n_days))) %>% \n ungroup(location_name, week) %>% \n right_join(tidyr::expand(., week, location_name)) %>% \n mutate(week = aweek::week2date(week))\n\n# create plot\nmetrics_plot <- ggplot(agg_weeks,\n aes(x = week,\n y = location_name,\n fill = p_days_reported))+\n geom_tile(colour=\"white\")+\n scale_fill_gradient(low = \"orange\", high = \"darkgreen\", na.value = \"grey80\")+\n scale_x_date(expand = c(0,0),\n date_breaks = \"2 weeks\",\n date_labels = \"%d\\n%b\")+\n theme_minimal()+ \n theme(\n legend.title = element_text(size=12, face=\"bold\"),\n legend.text = element_text(size=10, face=\"bold\"),\n legend.key.height = grid::unit(1,\"cm\"),\n legend.key.width = grid::unit(0.6,\"cm\"),\n axis.text.x = element_text(size=12),\n axis.text.y = element_text(vjust=0.2),\n axis.ticks = element_line(size=0.4),\n axis.title = element_text(size=12, face=\"bold\"),\n plot.title = element_text(hjust=0,size=14,face=\"bold\"),\n plot.caption = element_text(hjust = 0, face = \"italic\")\n )+\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\",\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, April-May 2019\",\n caption = \"7-day weeks beginning on Mondays.\")\n\nmetrics_plot # print\n\n\n\n\n\n\n\n\nBelow, we make it interactive and modify it for simple buttons and file size.\n\nmetrics_plot %>% \n plotly::ggplotly() %>% \n plotly::partial_bundle() %>% \n plotly::config(displaylogo = FALSE, modeBarButtonsToRemove = plotly_buttons_remove)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n–>", + "text": "39.4 Heat tiles\nYou can make almost any ggplot() plot interactive, including heat tiles. In the page on Heat plots you can read about how to make the below plot, which displays the proportion of days per week that certain facilities reported data to their province.\nHere is the code, although we will not describe it in depth here.\n\n# import data\nfacility_count_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\"))\n\n# aggregate data into Weeks for Spring district\nagg_weeks <- facility_count_data %>% \n filter(District == \"Spring\",\n data_date < as.Date(\"2020-08-01\")) %>% \n mutate(week = aweek::date2week(\n data_date,\n start_date = \"Monday\",\n floor_day = TRUE,\n factor = TRUE)) %>% \n group_by(location_name, week, .drop = F) %>%\n summarise(\n n_days = 7,\n n_reports = n(),\n malaria_tot = sum(malaria_tot, na.rm = T),\n n_days_reported = length(unique(data_date)),\n p_days_reported = round(100*(n_days_reported / n_days))) %>% \n ungroup(location_name, week) %>% \n right_join(tidyr::expand(., week, location_name)) %>% \n mutate(week = aweek::week2date(week))\n\n# create plot\nmetrics_plot <- ggplot(agg_weeks,\n aes(x = week,\n y = location_name,\n fill = p_days_reported)) +\n geom_tile(colour=\"white\") +\n scale_fill_gradient(low = \"orange\", high = \"darkgreen\", na.value = \"grey80\") +\n scale_x_date(expand = c(0,0),\n date_breaks = \"2 weeks\",\n date_labels = \"%d\\n%b\") +\n theme_minimal() + \n theme(\n legend.title = element_text(size=12, face=\"bold\"),\n legend.text = element_text(size=10, face=\"bold\"),\n legend.key.height = grid::unit(1,\"cm\"),\n legend.key.width = grid::unit(0.6,\"cm\"),\n axis.text.x = element_text(size=12),\n axis.text.y = element_text(vjust=0.2),\n axis.ticks = element_line(size=0.4),\n axis.title = element_text(size=12, face=\"bold\"),\n plot.title = element_text(hjust=0,size=14,face=\"bold\"),\n plot.caption = element_text(hjust = 0, face = \"italic\")\n ) +\n labs(x = \"Week\",\n y = \"Facility name\",\n fill = \"Reporting\\nperformance (%)\",\n title = \"Percent of days per week that facility reported data\",\n subtitle = \"District health facilities, April-May 2019\",\n caption = \"7-day weeks beginning on Mondays.\")\n\nmetrics_plot # print\n\n\n\n\n\n\n\n\nBelow, we make it interactive and modify it for simple buttons and file size.\n\nmetrics_plot %>% \n plotly::ggplotly() %>% \n plotly::partial_bundle() %>% \n plotly::config(displaylogo = FALSE, modeBarButtonsToRemove = plotly_buttons_remove)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n–>", "crumbs": [ "Data Visualization", "39  Interactive plots" @@ -3497,7 +3497,7 @@ "href": "new_pages/interactive_plots.html#resources", "title": "39  Interactive plots", "section": "39.5 Resources", - "text": "39.5 Resources\nPlotly is not just for R, but also works well with Python (and really any data science language as it’s built in JavaScript). You can read more about it on the plotly website", + "text": "39.5 Resources\nPlotly is not just for R, but also works well with Python and Julia (and really any data science language as it’s built in JavaScript). You can read more about it on the plotly website.", "crumbs": [ "Data Visualization", "39  Interactive plots" @@ -3508,7 +3508,7 @@ "href": "new_pages/rmarkdown.html", "title": "40  Reports with R Markdown", "section": "", - "text": "40.1 Preparation\nBackground to R Markdown\nTo explain some of the concepts and packages involved:\nIn sum, the process that happens in the background (you do not need to know all these steps!) involves feeding the .Rmd file to knitr, which executes the R code chunks and creates a new .md (markdown) file which includes the R code and its rendered output. The .md file is then processed by pandoc to create the finished product: a Microsoft Word document, HTML file, powerpoint document, pdf, etc.\n(source: https://rmarkdown.rstudio.com/authoring_quick_tour.html):\nInstallation\nTo create a R Markdown output, you need to have the following installed:\npacman::p_load(tinytex) # install tinytex package\ntinytex::install_tinytex() # R command to install TinyTeX software", + "text": "40.1 Preparation\nBackground to R Markdown\nTo explain some of the concepts and packages involved:\nIn sum, the process that happens in the background (you do not need to know all these steps!) involves feeding the .Rmd file to knitr, which executes the R code chunks and creates a new .md (markdown) file which includes the R code and its rendered output. The .md file is then processed by pandoc to create the finished product: a Microsoft Word document, HTML file, powerpoint document, pdf, etc.\nSource.\nInstallation\nTo create a R Markdown output, you need to have the following installed:\npacman::p_load(tinytex) # install tinytex package\ntinytex::install_tinytex() # R command to install TinyTeX software", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3519,7 +3519,7 @@ "href": "new_pages/rmarkdown.html#preparation", "title": "40  Reports with R Markdown", "section": "", - "text": "Markdown is a “language” that allows you to write a document using plain text, that can be converted to html and other formats. It is not specific to R. Files written in Markdown have a ‘.md’ extension.\nR Markdown: is a variation on markdown that is specific to R - it allows you to write a document using markdown to produce text and to embed R code and display their outputs. R Markdown files have ‘.Rmd’ extension.\n\nrmarkdown - the package: This is used by R to render the .Rmd file into the desired output. It’s focus is converting the markdown (text) syntax, so we also need…\nknitr: This R package will read the code chunks, execute it, and ‘knit’ it back into the document. This is how tables and graphs are included alongside the text.\nPandoc: Finally, pandoc actually convert the output into word/pdf/powerpoint etc. It is a software separate from R but is installed automatically with RStudio.\n\n\n\n\n\n\n\nThe rmarkdown package (knitr will also be installed automatically)\n\nPandoc, which should come installed with RStudio. If you are not using RStudio, you can download Pandoc here: http://pandoc.org.\nIf you want to generate PDF output (a bit trickier), you will need to install LaTeX. For R Markdown users who have not installed LaTeX before, we recommend that you install TinyTeX (https://yihui.name/tinytex/). You can use the following commands:", + "text": "Markdown is a “language” that allows you to write a document using plain text, that can be converted to html and other formats. It is not specific to R. Files written in Markdown have a ‘.md’ extension.\nR Markdown: is a variation on markdown that is specific to R - it allows you to write a document using markdown to produce text and to embed R code and display their outputs. R Markdown files have ‘.Rmd’ extension.\n\nrmarkdown - the package: This is used by R to render the .Rmd file into the desired output. It’s focus is converting the markdown (text) syntax, so we also need…\nknitr: This R package will read the code chunks, execute it, and ‘knit’ it back into the document. This is how tables and graphs are included alongside the text.\nPandoc: Finally, pandoc actually convert the output into word/pdf/powerpoint etc. It is a software separate from R but is installed automatically with RStudio.\n\n\n\n\n\n\n\nThe rmarkdown package (knitr will also be installed automatically)\nPandoc, which should come installed with RStudio. If you are not using RStudio, you can download Pandoc here.\nIf you want to generate PDF output (a bit trickier), you will need to install LaTeX. For R Markdown users who have not installed LaTeX before, we recommend that you install TinyTeX. You can use the following commands:", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3530,7 +3530,7 @@ "href": "new_pages/rmarkdown.html#getting-started", "title": "40  Reports with R Markdown", "section": "40.2 Getting started", - "text": "40.2 Getting started\n\nInstall rmarkdown R package\nInstall the rmarkdown R package. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(rmarkdown)\n\n\n\nStarting a new Rmd file\nIn RStudio, open a new R markdown file, starting with ‘File’, then ‘New file’ then ‘R markdown…’.\n\n\n\n\n\n\n\n\n\nR Studio will give you some output options to pick from. In the example below we select “HTML” because we want to create an html document. The title and the author names are not important. If the output document type you want is not one of these, don’t worry - you can just pick any one and change it in the script later.\n\n\n\n\n\n\n\n\n\nThis will open up a new .Rmd script.\n\n\nImportant to know\nThe working directory\nThe working directory of a markdown file is wherever the Rmd file itself is saved. For instance, if the R project is within ~/Documents/projectX and the Rmd file itself is in a subfolder ~/Documents/projectX/markdownfiles/markdown.Rmd, the code read.csv(“data.csv”) within the markdown will look for a csv file in the markdownfiles folder, and not the root project folder where scripts within projects would normally automatically look.\nTo refer to files elsewhere, you will either need to use the full file path or use the here package. The here package sets the working directory to the root folder of the R project and is explained in detail in the R projects and Import and export pages of this handbook. For instance, to import a file called “data.csv” from within the projectX folder, the code would be import(here(“data.csv”)).\nNote that use of setwd() in R Markdown scripts is not recommended – it only applies to the code chunk that it is written in.\nWorking on a drive vs your computer\nBecause R Markdown can run into pandoc issues when running on a shared network drive, it is recommended that your folder is on your local machine, e.g. in a project within ‘My Documents’. If you use Git (much recommended!), this will be familiar. For more details, see the handbook pages on R on network drives and Errors and help.", + "text": "40.2 Getting started\n\nInstall rmarkdown R package\nInstall the rmarkdown R package. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(rmarkdown)\n\n\n\nStarting a new Rmd file\nIn RStudio, open a new R markdown file, starting with ‘File’, then ‘New file’ then ‘R Markdown…’.\n\n\n\n\n\n\n\n\n\nR Studio will give you some output options to pick from. In the example below we select “HTML” because we want to create an html document. The title and the author names are not important. If the output document type you want is not one of these, don’t worry - you can just pick any one and change it in the script later. If you want to use the current date every time you render the document, such as for an automated report, you can click “Use current date when rendering document”, but this can also be updated later.\n\n\n\n\n\n\n\n\n\nThis will open up a new .Rmd script.\n\n\nImportant to know\nThe working directory\nThe working directory of a markdown file is wherever the Rmd file itself is saved. For instance, if the R project is within ~/Documents/projectX and the Rmd file itself is in a subfolder ~/Documents/projectX/markdownfiles/markdown.Rmd, the code read.csv(“data.csv”) within the markdown will look for a csv file in the markdownfiles folder, and not the root project folder where scripts within projects would normally automatically look.\nTo refer to files elsewhere, you will either need to use the full file path or use the here package. The here package sets the working directory to the root folder of the R project and is explained in detail in the R projects and Import and export pages of this handbook. For instance, to import a file called “data.csv” from within the projectX folder, the code would be import(here(“data.csv”)).\nNote that use of setwd() in R Markdown scripts is not recommended – it only applies to the code chunk that it is written in.\nWorking on a drive vs your computer\nBecause R Markdown can run into pandoc issues when running on a shared network drive, it is recommended that your folder is on your local machine, e.g. in a project within ‘My Documents’. If you use Git (much recommended!), this will be familiar. For more details, see the handbook pages on R on network drives and Errors and help.", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3541,7 +3541,7 @@ "href": "new_pages/rmarkdown.html#r-markdown-components", "title": "40  Reports with R Markdown", "section": "40.3 R Markdown components", - "text": "40.3 R Markdown components\nAn R Markdown document can be edited in RStudio just like a standard R script. When you start a new R Markdown script, RStudio tries to be helpful by showing a template which explains the different section of an R Markdown script.\nThe below is what appears when starting a new Rmd script intended to produce an html output (as per previous section).\n\n\n\n\n\n\n\n\n\nAs you can see, there are three basic components to an Rmd file: YAML, Markdown text, and R code chunks.\nThese will create and become your document output. See the diagram below:\n\n\n\n\n\n\n\n\n\n\nYAML metadata\nReferred to as the ‘YAML metadata’ or just ‘YAML’, this is at the top of the R Markdown document. This section of the script will tell your Rmd file what type of output to produce, formatting preferences, and other metadata such as document title, author, and date. There are other uses not mentioned here (but referred to in ‘Producing an output’). Note that indentation matters; tabs are not accepted but spaces are.\nThis section must begin with a line containing just three dashes --- and must close with a line containing just three dashes ---. YAML parameters comes in key:value pairs. The placement of colons in YAML is important - the key:value pairs are separated by colons (not equals signs!).\nThe YAML should begin with metadata for the document. The order of these primary YAML parameters (not indented) does not matter. For example:\ntitle: \"My document\"\nauthor: \"Me\"\ndate: \"2024-06-19\"\nYou can use R code in YAML values by writing it as in-line code (preceded by r within back-ticks) but also within quotes (see above example for date:).\nIn the image above, because we clicked that our default output would be an html file, we can see that the YAML says output: html_document. However we can also change this to say powerpoint_presentation or word_document or even pdf_document.\n\n\nText\nThis is the narrative of your document, including the titles and headings. It is written in the “markdown” language, which is used across many different software.\nBelow are the core ways to write this text. See more extensive documentation available on R Markdown “cheatsheet” at the RStudio website.\n\nNew lines\nUniquely in R Markdown, to initiate a new line, enter *two spaces** at the end of the previous line and then Enter/Return.\n\n\nCase\nSurround your normal text with these character to change how it appears in the output.\n\nUnderscores (_text_) or single asterisk (*text*) to italicise\nDouble asterisks (**text**) for bold text\nBack-ticks (text) to display text as code\n\nThe actual appearance of the font can be set by using specific templates (specified in the YAML metadata; see example tabs).\n\n\nColor\nThere is no simple mechanism to change the color of text in R Markdown. One work-around, IF your output is an HTML file, is to add an HTML line into the markdown text. The below HTML code will print a line of text in bold red.\n<span style=\"color: red;\">**_DANGER:_** This is a warning.</span> \nDANGER: This is a warning.\n\n\nTitles and headings\nA hash symbol in a text portion of a R Markdown script creates a heading. This is different than in a chunk of R code in the script, in which a hash symbol is a mechanism to comment/annotate/de-activate, as in a normal R script.\nDifferent heading levels are established with different numbers of hash symbols at the start of a new line. One hash symbol is a title or primary heading. Two hash symbols are a second-level heading. Third- and fourth-level headings can be made with successively more hash symbols.\n# First-level heading / title\n\n## Second level heading \n\n### Third-level heading\n\n\nBullets and numbering\nUse asterisks (*) to created a bullets list. Finish the previous sentence, enter two spaces, Enter/Return twice, and then start your bullets. Include a space between the asterisk and your bullet text. After each bullet enter two spaces and then Enter/Return. Sub-bullets work the same way but are indented. Numbers work the same way but instead of an asterisk, write 1), 2), etc. Below is how your R Markdown script text might look.\nHere are my bullets (there are two spaces after this colon): \n\n* Bullet 1 (followed by two spaces and Enter/Return) \n* Bullet 2 (followed by two spaces and Enter/Return) \n * Sub-bullet 1 (followed by two spaces and Enter/Return) \n * Sub-bullet 2 (followed by two spaces and Enter/Return) \n \n\n\nComment out text\nYou can “comment out” R Markdown text just as you can use the “#” to comment out a line of R code in an R chunk. Simply highlight the text and press Ctrl+Shift+c (Cmd+Shift+c for Mac). The text will be surrounded by arrows and turn green. It will not appear in your output.\n\n\n\n\n\n\n\n\n\n\n\n\nCode chunks\nSections of the script that are dedicated to running R code are called “chunks”. This is where you may load packages, import data, and perform the actual data management and visualisation. There may be many code chunks, so they can help you organize your R code into parts, perhaps interspersed with text. To note: These ‘chunks’ will appear to have a slightly different background colour from the narrative part of the document.\nEach chunk is opened with a line that starts with three back-ticks, and curly brackets that contain parameters for the chunk ({ }). The chunk ends with three more back-ticks.\nYou can create a new chunk by typing it out yourself, by using the keyboard shortcut “Ctrl + Alt + i” (or Cmd + Shift + r in Mac), or by clicking the green ‘insert a new code chunk’ icon at the top of your script editor.\nSome notes about the contents of the curly brackets { }:\n\nThey start with ‘r’ to indicate that the language name within the chunk is R\nAfter the r you can optionally write a chunk “name” – these are not necessary but can help you organise your work. Note that if you name your chunks, you should ALWAYS use unique names or else R will complain when you try to render.\n\nThe curly brackets can include other options too, written as tag=value, such as:\n\neval = FALSE to not run the R code\n\necho = FALSE to not print the chunk’s R source code in the output document\n\nwarning = FALSE to not print warnings produced by the R code\n\nmessage = FALSE to not print any messages produced by the R code\n\ninclude = either TRUE/FALSE whether to include chunk outputs (e.g. plots) in the document\nout.width = and out.height = - provide in style out.width = \"75%\"\n\nfig.align = \"center\" adjust how a figure is aligned across the page\n\nfig.show='hold' if your chunk prints multiple figures and you want them printed next to each other (pair with out.width = c(\"33%\", \"67%\"). Can also set as fig.show='asis' to show them below the code that generates them, 'hide' to hide, or 'animate' to concatenate multiple into an animation.\n\nA chunk header must be written in one line\n\nTry to avoid periods, underscores, and spaces. Use hyphens ( - ) instead if you need a separator.\n\nRead more extensively about the knitr options here.\nSome of the above options can be configured with point-and-click using the setting buttons at the top right of the chunk. Here, you can specify which parts of the chunk you want the rendered document to include, namely the code, the outputs, and the warnings. This will come out as written preferences within the curly brackets, e.g. echo=FALSE if you specify you want to ‘Show output only’.\n\n\n\n\n\n\n\n\n\nThere are also two arrows at the top right of each chunk, which are useful to run code within a chunk, or all code in prior chunks. Hover over them to see what they do.\nFor global options to be applied to all chunks in the script, you can set this up within your very first R code chunk in the script. For instance, so that only the outputs are shown for each code chunk and not the code itself, you can include this command in the R code chunk:\n\nknitr::opts_chunk$set(echo = FALSE) \n\n\nIn-text R code\nYou can also include minimal R code within back-ticks. Within the back-ticks, begin the code with “r” and a space, so RStudio knows to evaluate the code as R code. See the example below.\nThe example below shows multiple heading levels, bullets, and uses R code for the current date (Sys.Date()) to evaluate into a printed date.\n\n\n\n\n\n\n\n\n\nThe example above is simple (showing the current date), but using the same syntax you can display values produced by more complex R code (e.g. to calculate the min, median, max of a column). You can also integrate R objects or values that were created in R code chunks earlier in the script.\nAs an example, the script below calculates the proportion of cases that are aged less than 18 years old, using tidyverse functions, and creates the objects less18, total, and less18prop. This dynamic value is inserted into subsequent text. We see how it looks when knitted to a word document.\n\n\n\n\n\n\n\n\n\n\n\n\nImages\nYou can include images in your R Markdown one of two ways:\n\n![](\"path/to/image.png\") \n\nIf the above does not work, try using knitr::include_graphics()\n\nknitr::include_graphics(\"path/to/image.png\")\n\n(remember, your file path could be written using the here package)\n\nknitr::include_graphics(here::here(\"path\", \"to\", \"image.png\"))\n\n\n\nTables\nCreate a table using hyphens ( - ) and bars ( | ). The number of hyphens before/between bars allow the number of spaces in the cell before the text begins to wrap.\nColumn 1 |Column 2 |Column 3\n---------|----------|--------\nCell A |Cell B |Cell C\nCell D |Cell E |Cell F\nThe above code produces the table below:\n\n\n\nColumn 1\nColumn 2\nColumn 3\n\n\n\n\nCell A\nCell B\nCell C\n\n\nCell D\nCell E\nCell F\n\n\n\n\n\nTabbed sections\nFor HTML outputs, you can arrange the sections into “tabs”. Simply add .tabset in the curly brackets { } that are placed after a heading. Any sub-headings beneath that heading (until another heading of the same level) will appear as tabs that the user can click through. Read more here\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYou can add an additional option .tabset-pills after .tabset to give the tabs themselves a “pilled” appearance. Be aware that when viewing the tabbed HTML output, the Ctrl+f search functionality will only search “active” tabs, not hidden tabs.", + "text": "40.3 R Markdown components\nAn R Markdown document can be edited in RStudio just like a standard R script. When you start a new R Markdown script, RStudio tries to be helpful by showing a template which explains the different section of an R Markdown script.\nThe below is what appears when starting a new Rmd script intended to produce an html output (as per previous section).\n\n\n\n\n\n\n\n\n\nAs you can see, there are three basic components to an Rmd file: YAML, Markdown text, and R code chunks.\nThese will create and become your document output. See the diagram below:\n\n\n\n\n\n\n\n\n\n\nYAML metadata\nReferred to as the ‘YAML metadata’ or just ‘YAML’, this is at the top of the R Markdown document. This section of the script will tell your Rmd file what type of output to produce, formatting preferences, and other metadata such as document title, author, and date. There are other uses not mentioned here (but referred to in Producing the document. Note that indentation matters; tabs are not accepted but spaces are.\nThis section must begin with a line containing just three dashes --- and must close with a line containing just three dashes ---. YAML parameters comes in key:value pairs. The placement of colons in YAML is important - the key:value pairs are separated by colons (not equals signs!).\nThe YAML should begin with metadata for the document. The order of these primary YAML parameters (not indented) does not matter. For example:\ntitle: \"My document\"\nauthor: \"Me\"\ndate: \"2024-10-18\"\nYou can use R code in YAML values by writing it as in-line code (preceded by r within back-ticks) but also within quotes (see above example for date:).\nIn the image above, because we clicked that our default output would be an html file, we can see that the YAML says output: html_document. However we can also change this to say powerpoint_presentation or word_document or even pdf_document.\n\n\nText\nThis is the narrative of your document, including the titles and headings. It is written in the “markdown” language, which is used across many different software.\nBelow are the core ways to write this text. See more extensive documentation available on R Markdown “cheatsheet” at the RStudio website.\n\nNew lines\nUniquely in R Markdown, to initiate a new line, enter two spaces at the end of the previous line and then Enter/Return.\n\n\nCase\nSurround your normal text with these character to change how it appears in the output.\n\nUnderscores (_text_) or single asterisk (*text*) to italicise\nDouble asterisks (**text**) for bold text\nBack-ticks (text) to display text as code\n\nThe actual appearance of the font can be set by using specific templates (specified in the YAML metadata; see example tabs).\n\n\nColor\nThere is no simple mechanism to change the color of text in R Markdown. One work-around, IF your output is an HTML file, is to add an HTML line into the markdown text. The below HTML code will print a line of text in bold red.\n<span style=\"color: red;\">**_DANGER:_** This is a warning.</span> \nDANGER: This is a warning.\n\n\nTitles and headings\nA hash symbol in a text portion of a R Markdown script creates a heading. This is different than in a chunk of R code in the script, in which a hash symbol is a mechanism to comment/annotate/de-activate, as in a normal R script.\nDifferent heading levels are established with different numbers of hash symbols at the start of a new line. One hash symbol is a title or primary heading. Two hash symbols are a second-level heading. Third- and fourth-level headings can be made with successively more hash symbols.\n# First-level heading / title\n\n## Second level heading \n\n### Third-level heading\n\n\nBullets and numbering\nUse asterisks (*) to created a bullets list. Finish the previous sentence, enter two spaces, Enter/Return twice, and then start your bullets. Include a space between the asterisk and your bullet text. After each bullet enter two spaces and then Enter/Return. Sub-bullets work the same way but are indented. Numbers work the same way but instead of an asterisk, write 1), 2), etc. Below is how your R Markdown script text might look.\nHere are my bullets (there are two spaces after this colon): \n\n* Bullet 1 (followed by two spaces and Enter/Return) \n* Bullet 2 (followed by two spaces and Enter/Return) \n * Sub-bullet 1 (followed by two spaces and Enter/Return) \n * Sub-bullet 2 (followed by two spaces and Enter/Return) \n \n\n\nComment out text\nYou can “comment out” R Markdown text just as you can use the “#” to comment out a line of R code in an R chunk. Simply highlight the text and press Ctrl+Shift+c (Cmd+Shift+c for Mac). The text will be surrounded by arrows and turn green. It will not appear in your output.\n\n\n\n\n\n\n\n\n\n\n\n\nCode chunks\nSections of the script that are dedicated to running R code are called “chunks”. This is where you may load packages, import data, and perform the actual data management and visualisation. There may be many code chunks, so they can help you organize your R code into parts, perhaps interspersed with text. To note: These ‘chunks’ will appear to have a slightly different background colour from the narrative part of the document.\nEach chunk is opened with a line that starts with three back-ticks, and curly brackets that contain parameters for the chunk ({ }). The chunk ends with three more back-ticks.\nYou can create a new chunk by typing it out yourself, by using the keyboard shortcut “Ctrl + Alt + i” (or Cmd + Shift + r in Mac), or by clicking the green ‘insert a new code chunk’ icon at the top of your script editor.\nSome notes about the contents of the curly brackets { }:\n\nThey start with ‘r’ to indicate that the language name within the chunk is R\nAfter the r you can optionally write a chunk “name” – these are not necessary but can help you organise your work. Note that if you name your chunks, you should ALWAYS use unique names or else R will complain when you try to render\n\nThe curly brackets can include other options too, written as tag=value, such as:\n\neval = FALSE to not run the R code\necho = FALSE to not print the chunk’s R source code in the output document\nwarning = FALSE to not print warnings produced by the R code\nmessage = FALSE to not print any messages produced by the R code\ninclude = either TRUE/FALSE whether to include chunk outputs (e.g. plots) in the document\nout.width = and out.height = - provide in style out.width = \"75%\"\nfig.align = \"center\" adjust how a figure is aligned across the page\nfig.show='hold' if your chunk prints multiple figures and you want them printed next to each other (pair with out.width = c(\"33%\", \"67%\"). Can also set as fig.show='asis' to show them below the code that generates them, 'hide' to hide, or 'animate' to concatenate multiple into an animation\nA chunk header must be written in one line\nTry to avoid periods, underscores, and spaces. Use hyphens ( - ) instead if you need a separator\n\nRead more extensively about the knitr options here.\nSome of the above options can be configured with point-and-click using the setting buttons at the top right of the chunk. Here, you can specify which parts of the chunk you want the rendered document to include, namely the code, the outputs, and the warnings. This will come out as written preferences within the curly brackets, e.g. echo=FALSE if you specify you want to ‘Show output only’.\n\n\n\n\n\n\n\n\n\nThere are also two arrows at the top right of each chunk, which are useful to run code within a chunk, or all code in prior chunks. Hover over them to see what they do.\nFor global options to be applied to all chunks in the script, you can set this up within your very first R code chunk in the script. For instance, so that only the outputs are shown for each code chunk and not the code itself, you can include this command in the R code chunk:\n\nknitr::opts_chunk$set(echo = FALSE) \n\n\nIn-text R code\nYou can also include minimal R code within back-ticks. Within the back-ticks, begin the code with “r” and a space, so RStudio knows to evaluate the code as R code. See the example below.\nThe example below shows multiple heading levels, bullets, and uses R code for the current date (Sys.Date()) to evaluate into a printed date.\n\n\n\n\n\n\n\n\n\nThe example above is simple (showing the current date), but using the same syntax you can display values produced by more complex R code (e.g. to calculate the min, median, max of a column). You can also integrate R objects or values that were created in R code chunks earlier in the script.\nAs an example, the script below calculates the proportion of cases that are aged less than 18 years old, using tidyverse functions, and creates the objects less18, total, and less18prop. This dynamic value is inserted into subsequent text. We see how it looks when knitted to a word document.\n\n\n\n\n\n\n\n\n\n\n\n\nImages\nYou can include images in your R Markdown one of two ways:\n\n![](\"path/to/image.png\") \n\nIf the above does not work, try using knitr::include_graphics()\n\nknitr::include_graphics(\"path/to/image.png\")\n\n(remember, your file path could be written using the here package)\n\nknitr::include_graphics(here::here(\"path\", \"to\", \"image.png\"))\n\n\n\nTables\nCreate a table using hyphens ( - ) and bars ( | ). The number of hyphens before/between bars allow the number of spaces in the cell before the text begins to wrap.\nColumn 1 |Column 2 |Column 3\n---------|----------|--------\nCell A |Cell B |Cell C\nCell D |Cell E |Cell F\nThe above code produces the table below:\n\n\n\nColumn 1\nColumn 2\nColumn 3\n\n\n\n\nCell A\nCell B\nCell C\n\n\nCell D\nCell E\nCell F\n\n\n\n\n\nTabbed sections\nFor HTML outputs, you can arrange the sections into “tabs”. Simply add .tabset in the curly brackets { } that are placed after a heading. Any sub-headings beneath that heading (until another heading of the same level) will appear as tabs that the user can click through. Read more here.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYou can add an additional option .tabset-pills after .tabset to give the tabs themselves a “pilled” appearance. Be aware that when viewing the tabbed HTML output, the Ctrl+f search functionality will only search “active” tabs, not hidden tabs.\n\n\nremedy\nremedy is an addin for R-Studio which helps with writing R Markdown scripts. It provides a user interface and series of keyboard shortcuts to format your text.\nThis package is installed directly from GitHub.\n\nremotes::install_github(\"ThinkR-open/remedy\")\n\nOnce installed, the package does not need to be re-loaded. It will automatically load when you start RStudio.\nFor a full list of features and updates, please see https://thinkr-open.github.io/remedy/", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3552,7 +3552,7 @@ "href": "new_pages/rmarkdown.html#file-structure", "title": "40  Reports with R Markdown", "section": "40.4 File structure", - "text": "40.4 File structure\nThere are several ways to structure your R Markdown and any associated R scripts. Each has advantages and disadvantages:\n\nSelf-contained R Markdown - everything needed for the report is imported or created within the R Markdown\n\nSource other files - You can run external R scripts with the source() command and use their outputs in the Rmd\n\nChild scripts - an alternate mechanism for source()\n\n\nUtilize a “runfile” - Run commands in an R script prior to rendering the R Markdown\n\n\nSelf-contained Rmd\nFor a relatively simple report, you may elect to organize your R Markdown script such that it is “self-contained” and does not involve any external scripts.\nEverything you need to run the R markdown is imported or created within the Rmd file, including all the code chunks and package loading. This “self-contained” approach is appropriate when you do not need to do much data processing (e.g. it brings in a clean or semi-clean data file) and the rendering of the R Markdown will not take too long.\nIn this scenario, one logical organization of the R Markdown script might be:\n\nSet global knitr options\n\nLoad packages\n\nImport data\n\nProcess data\n\nProduce outputs (tables, plots, etc.)\n\nSave outputs, if applicable (.csv, .png, etc.)\n\n\nSource other files\nOne variation of the “self-contained” approach is to have R Markdown code chunks “source” (run) other R scripts. This can make your R Markdown script less cluttered, more simple, and easier to organize. It can also help if you want to display final figures at the beginning of the report. In this approach, the final R Markdown script simply combines pre-processed outputs into a document.\nOne way to do this is by providing the R scripts (file path and name with extension) to the base R command source().\n\nsource(\"your-script.R\", local = knitr::knit_global())\n# or sys.source(\"your-script.R\", envir = knitr::knit_global())\n\nNote that when using source() within the R Markdown, the external files will still be run during the course of rendering your Rmd file. Therefore, each script is run every time you render the report. Thus, having these source() commands within the R Markdown does not speed up your run time, nor does it greatly assist with de-bugging, as error produced will still be printed when producing the R Markdown.\nAn alternative is to utilize the child = knitr option. EXPLAIN MORE TO DO\nYou must be aware of various R environments. Objects created within an environment will not necessarily be available to the environment used by the R Markdown.\n\n\n\nRunfile\nThis approach involves utilizing the R script that contains the render() command(s) to pre-process objects that feed into the R markdown.\nFor instance, you can load the packages, load and clean the data, and even create the graphs of interest prior to render(). These steps can occur in the R script, or in other scripts that are sourced. As long as these commands occur in the same RStudio session and objects are saved to the environment, the objects can then be called within the Rmd content. Then the R markdown itself will only be used for the final step - to produce the output with all the pre-processed objects. This is much easier to de-bug if something goes wrong.\nThis approach is helpful for the following reasons:\n\nMore informative error messages - these messages will be generated from the R script, not the R Markdown. R Markdown errors tend to tell you which chunk had a problem, but will not tell you which line.\n\nIf applicable, you can run long processing steps in advance of the render() command - they will run only once.\n\nIn the example below, we have a separate R script in which we pre-process a data object into the R Environment and then render the “create_output.Rmd” using render().\n\ndata <- import(\"datafile.csv\") %>% # Load data and save to environment\n select(age, hospital, weight) # Select limited columns\n\nrmarkdown::render(input = \"create_output.Rmd\") # Create Rmd file\n\n\n\nFolder strucutre\nWorkflow also concerns the overall folder structure, such as having an ‘output’ folder for created documents and figures, and ‘data’ or ‘inputs’ folders for cleaned data. We do not go into further detail here, but check out the Organizing routine reports page.", + "text": "40.4 File structure\nThere are several ways to structure your R Markdown and any associated R scripts. Each has advantages and disadvantages:\n\nSelf-contained R Markdown - everything needed for the report is imported or created within the R Markdown.\n\nSource other files - You can run external R scripts with the source() command and use their outputs in the Rmd.\n\nChild scripts - an alternate mechanism for source().\n\n\nUtilize a “runfile” - Run commands in an R script prior to rendering the R Markdown.\n\n\nSelf-contained Rmd\nFor a relatively simple report, you may elect to organize your R Markdown script such that it is “self-contained” and does not involve any external scripts.\nEverything you need to run the R markdown is imported or created within the Rmd file, including all the code chunks and package loading. This “self-contained” approach is appropriate when you do not need to do much data processing (e.g. it brings in a clean or semi-clean data file) and the rendering of the R Markdown will not take too long.\nIn this scenario, one logical organization of the R Markdown script might be:\n\nSet global knitr options\nLoad packages\nImport data\nProcess data\nProduce outputs (tables, plots, etc.)\nSave outputs, if applicable (.csv, .png, etc.)\n\n\nSource other files\nOne variation of the “self-contained” approach is to have R Markdown code chunks “source” (run) other R scripts. This can make your R Markdown script less cluttered, more simple, and easier to organize. It can also help if you want to display final figures at the beginning of the report. In this approach, the final R Markdown script simply combines pre-processed outputs into a document.\nOne way to do this is by providing the R scripts (file path and name with extension) to the base R command source().\n\nsource(\"your-script.R\", local = knitr::knit_global())\n# or sys.source(\"your-script.R\", envir = knitr::knit_global())\n\nNote that when using source() within the R Markdown, the external files will still be run during the course of rendering your Rmd file. Therefore, each script is run every time you render the report. Thus, having these source() commands within the R Markdown does not speed up your run time, nor does it greatly assist with de-bugging, as error produced will still be printed when producing the R Markdown.\nAn alternative is to utilize the child = knitr option.\n\n\nchild =\nIf you have a very long R markdown document, with several distinct sections, you might want to create several smaller documents (referred to as child documents), and combine them into the finished report.\nThis approach can be particularly useful if you want to have the option to include or omit certain sections or analyses when you re-run the R markdown document. For instance, for a daily update on an outbreak you may only want a rapid, streamlined, update, but for a weekly report you may want a more in depth analysis.\nFor example, imagine you have a report that creates a summary of an outbreak, and every Sunday it runs additional analysis looking at the demographics of the outbreak. Here we will have our main R markdown file “main_report.Rmd”, and the separate analysis in the file “demographic_analysis.Rmd”.\nHere we have our document main_report.Rmd which runs the standard analysis, and we have an additional section where we will call the file demographic_analysis.Rmd. This is done by specifying the .Rmd file we want in an R code block. This allows us to use the function knit_child() from the kintr package to call the .Rmd file and knit it within our primary R markdown file.\nWe have additionally included an if() and ifelse() statement to check if the day of the week (using the function wday() from the lubridate package) is Sunday, the day of the week we want the additional analysis to be run.\n\n\n\n\n\n\n\n\n\nIf the day is Sunday, then it will knit demographic_analysis.Rmd into the main document.\nFor example, the output when it is not Sunday.\n\n\n\n\n\n\n\n\n\nand when it is Sunday.\n\n\n\n\n\n\n\n\n\nNote: Your code chunks cannot share names between the main document and the child documents, or it wil not run.\nYou must be aware of various R environments. Objects created within an environment will not necessarily be available to the environment used by the R Markdown.\n\n\n\nRunfile\nThis approach involves utilizing the R script that contains the render() command(s) to pre-process objects that feed into the R markdown.\nFor instance, you can load the packages, load and clean the data, and even create the graphs of interest prior to render(). These steps can occur in the R script, or in other scripts that are sourced. As long as these commands occur in the same RStudio session and objects are saved to the environment, the objects can then be called within the Rmd content. Then the R markdown itself will only be used for the final step - to produce the output with all the pre-processed objects. This is much easier to de-bug if something goes wrong.\nThis approach is helpful for the following reasons:\n\nMore informative error messages - these messages will be generated from the R script, not the R Markdown. R Markdown errors tend to tell you which chunk had a problem, but will not tell you which line\nIf applicable, you can run long processing steps in advance of the render() command - they will run only once\n\nIn the example below, we have a separate R script in which we pre-process a data object into the R Environment and then render the “create_output.Rmd” using render().\n\ndata <- import(\"datafile.csv\") %>% # Load data and save to environment\n select(age, hospital, weight) # Select limited columns\n\nrmarkdown::render(input = \"create_output.Rmd\") # Create Rmd file\n\n\n\nFolder strucutre\nWorkflow also concerns the overall folder structure, such as having an ‘output’ folder for created documents and figures, and ‘data’ or ‘inputs’ folders for cleaned data. We do not go into further detail here, but please refer to the Organizing routine reports page.", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3563,7 +3563,7 @@ "href": "new_pages/rmarkdown.html#producing-the-document", "title": "40  Reports with R Markdown", "section": "40.5 Producing the document", - "text": "40.5 Producing the document\nYou can produce the document in the following ways:\n\nManually by pressing the “Knit” button at the top of the RStudio script editor (fast and easy)\n\nRun the render() command (executed outside the R Markdown script)\n\n\nOption 1: “Knit” button\nWhen you have the Rmd file open, press the ‘Knit’ icon/button at the top of the file.\nR Studio will you show the progress within an ‘R Markdown’ tab near your R console. The document will automatically open when complete.\nThe document will be saved in the same folder as your R markdown script, and with the same file name (aside from the extension). This is obviously not ideal for version control (it will be over-written each tim you knit, unless moved manually), as you may then need to rename the file yourself (e.g. add a date).\nThis is RStudio’s shortcut button for the render() function from rmarkdown. This approach only compatible with a self-contained R markdown, where all the needed components exist or are sourced within the file.\n\n\n\n\n\n\n\n\n\n\n\nOption 2: render() command\nAnother way to produce your R Markdown output is to run the render() function (from the rmarkdown package). You must execute this command outside the R Markdown script - so either in a separate R script (often called a “run file”), or as a stand-alone command in the R Console.\n\nrmarkdown::render(input = \"my_report.Rmd\")\n\nAs with “knit”, the default settings will save the Rmd output to the same folder as the Rmd script, with the same file name (aside from the file extension). For instance “my_report.Rmd” when knitted will create “my_report.docx” if you are knitting to a word document. However, by using render() you have the option to use different settings. render() can accept arguments including:\n\noutput_format = This is the output format to convert to (e.g. \"html_document\", \"pdf_document\", \"word_document\", or \"all\"). You can also specify this in the YAML inside the R Markdown script.\n\noutput_file = This is the name of the output file (and file path). This can be created via R functions like here() or str_glue() as demonstrated below.\n\noutput_dir = This is an output directory (folder) to save the file. This allows you to chose an alternative other than the directory the Rmd file is saved to.\n\noutput_options = You can provide a list of options that will override those in the script YAML (e.g. )\noutput_yaml = You can provide path to a .yml file that contains YAML specifications\n\nparams = See the section on parameters below\n\nSee the complete list here\n\nAs one example, to improve version control, the following command will save the output file within an ‘outputs’ sub-folder, with the current date in the file name. To create the file name, the function str_glue() from the stringr package is use to ‘glue’ together static strings (written plainly) with dynamic R code (written in curly brackets). For instance if it is April 10th 2021, the file name from below will be “Report_2021-04-10.docx”. See the page on Characters and strings for more details on str_glue().\n\nrmarkdown::render(\n input = \"create_output.Rmd\",\n output_file = stringr::str_glue(\"outputs/Report_{Sys.Date()}.docx\")) \n\nAs the file renders, the RStudio Console will show you the rendering progress up to 100%, and a final message to indicate that the rendering is complete.\n\n\nOptions 3: reportfactory package\nThe R package reportfactory offers an alternative method of organising and compiling R Markdown reports catered to scenarios where you run reports routinely (e.g. daily, weekly…). It eases the compilation of multiple R Markdown files and the organization of their outputs. In essence, it provides a “factory” from which you can run the R Markdown reports, get automatically date- and time-stamped folders for the outputs, and have “light” version control.\nRead more about this work flow in the page on Organizing routine reports.", + "text": "40.5 Producing the document\nYou can produce the document in the following ways:\n\nManually by pressing the “Knit” button at the top of the RStudio script editor (fast and easy)\n\nRun the render() command (executed outside the R Markdown script)\n\n\nOption 1: “Knit” button\nWhen you have the Rmd file open, press the ‘Knit’ icon/button at the top of the file.\nR Studio will you show the progress within an ‘R Markdown’ tab near your R console. The document will automatically open when complete.\nThe document will be saved in the same folder as your R markdown script, and with the same file name (aside from the extension). This is obviously not ideal for version control (it will be over-written each tim you knit, unless moved manually), as you may then need to rename the file yourself (e.g. add a date).\nThis is RStudio’s shortcut button for the render() function from rmarkdown. This approach only compatible with a self-contained R markdown, where all the needed components exist or are sourced within the file.\n\n\n\n\n\n\n\n\n\n\n\nOption 2: render() command\nAnother way to produce your R Markdown output is to run the render() function (from the rmarkdown package). You must execute this command outside the R Markdown script - so either in a separate R script (often called a “run file”), or as a stand-alone command in the R Console.\n\nrmarkdown::render(input = \"my_report.Rmd\")\n\nAs with “knit”, the default settings will save the Rmd output to the same folder as the Rmd script, with the same file name (aside from the file extension). For instance “my_report.Rmd” when knitted will create “my_report.docx” if you are knitting to a word document. However, by using render() you have the option to use different settings. render() can accept arguments including:\n\noutput_format = This is the output format to convert to (e.g. \"html_document\", \"pdf_document\", \"word_document\", or \"all\"). You can also specify this in the YAML inside the R Markdown script\noutput_file = This is the name of the output file (and file path). This can be created via R functions like here() or str_glue() as demonstrated below\noutput_dir = This is an output directory (folder) to save the file. This allows you to chose an alternative other than the directory the Rmd file is saved to\noutput_options = You can provide a list of options that will override those in the script YAML (e.g. )\noutput_yaml = You can provide path to a .yml file that contains YAML specifications\n\nparams = See the section on parameters below\n\nSee the complete list here\n\nAs one example, to improve version control, the following command will save the output file within an ‘outputs’ sub-folder, with the current date in the file name. To create the file name, the function str_glue() from the stringr package is use to ‘glue’ together static strings (written plainly) with dynamic R code (written in curly brackets). For instance if it is April 10th 2021, the file name from below will be “Report_2021-04-10.docx”. See the page on Characters and strings for more details on str_glue().\n\nrmarkdown::render(\n input = \"create_output.Rmd\",\n output_file = stringr::str_glue(\"outputs/Report_{Sys.Date()}.docx\")) \n\nAs the file renders, the RStudio Console will show you the rendering progress up to 100%, and a final message to indicate that the rendering is complete.\n\n\nOptions 3: reportfactory package\nThe R package reportfactory offers an alternative method of organising and compiling R Markdown reports catered to scenarios where you run reports routinely (e.g. daily, weekly…). It eases the compilation of multiple R Markdown files and the organization of their outputs. In essence, it provides a “factory” from which you can run the R Markdown reports, get automatically date- and time-stamped folders for the outputs, and have “light” version control.\nRead more about this work flow in the page on Organizing routine reports.", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3574,7 +3574,7 @@ "href": "new_pages/rmarkdown.html#parameterised-reports", "title": "40  Reports with R Markdown", "section": "40.6 Parameterised reports", - "text": "40.6 Parameterised reports\nYou can use parameterisation to make a report dynamic, such that it can be run with specific setting (e.g. a specific date or place or with certain knitting options). Below, we focus on the basics, but there is more detail online about parameterized reports.\nUsing the Ebola linelist as an example, let’s say we want to run a standard surveillance report for each hospital each day. We show how one can do this using parameters.\nImportant: dynamic reports are also possible without the formal parameter structure (without params:), using simple R objects in an adjacent R script. This is explained at the end of this section.\n\nSetting parameters\nYou have several options for specifying parameter values for your R Markdown output.\n\nOption 1: Set parameters within YAML\nEdit the YAML to include a params: option, with indented statements for each parameter you want to define. In this example we create parameters date and hospital, for which we specify values. These values are subject to change each time the report is run. If you use the “Knit” button to produce the output, the parameters will have these default values. Likewise, if you use render() the parameters will have these default values unless otherwise specified in the render() command.\n---\ntitle: Surveillance report\noutput: html_document\nparams:\n date: 2021-04-10\n hospital: Central Hospital\n---\nIn the background, these parameter values are contained within a read-only list called params. Thus, you can insert the parameter values in R code as you would another R object/value in your environment. Simply type params$ followed by the parameter name. For example params$hospital to represent the hospital name (“Central Hospital” by default).\nNote that parameters can also hold values true or false, and so these can be included in your knitr options for a R chunk. For example, you can set {r, eval=params$run} instead of {r, eval=FALSE}, and now whether the chunk runs or not depends on the value of a parameter run:.\nNote that for parameters that are dates, they will be input as a string. So for params$date to be interpreted in R code it will likely need to be wrapped with as.Date() or a similar function to convert to class Date.\n\n\nOption 2: Set parameters within render()\nAs mentioned above, as alternative to pressing the “Knit” button to produce the output is to execute the render() function from a separate script. In this later case, you can specify the parameters to be used in that rendering to the params = argument of render().\nNote than any parameter values provided here will overwrite their default values if written within the YAML. We write the values in quotation marks as in this case they should be defined as character/string values.\nThe below command renders “surveillance_report.Rmd”, specifies a dynamic output file name and folder, and provides a list() of two parameters and their values to the argument params =.\n\nrmarkdown::render(\n input = \"surveillance_report.Rmd\", \n output_file = stringr::str_glue(\"outputs/Report_{Sys.Date()}.docx\"),\n params = list(date = \"2021-04-10\", hospital = \"Central Hospital\"))\n\n\n\nOption 3: Set parameters using a Graphical User Interface\nFor a more interactive feel, you can also use the Graphical User Interface (GUI) to manually select values for parameters. To do this we can click the drop-down menu next to the ‘Knit’ button and choose ‘Knit with parameters’.\nA pop-up will appear allowing you to type in values for the parameters that are established in the document’s YAML.\n\n\n\n\n\n\n\n\n\nYou can achieve the same through a render() command by specifying params = \"ask\", as demonstrated below.\n\nrmarkdown::render(\n input = \"surveillance_report.Rmd\", \n output_file = stringr::str_glue(\"outputs/Report_{Sys.Date()}.docx\"),\n params = “ask”)\n\nHowever, typing values into this pop-up window is subject to error and spelling mistakes. You may prefer to add restrictions to the values that can be entered through drop-down menus. You can do this by adding in the YAML several specifications for each params: entry.\n\nlabel: is how the title for that particular drop-down menu\n\nvalue: is the default (starting) value\n\ninput: set to select for drop-down menu\n\nchoices: Give the eligible values in the drop-down menu\n\nBelow, these specifications are written for the hospital parameter.\n---\ntitle: Surveillance report\noutput: html_document\nparams:\n date: 2021-04-10\n hospital: \n label: “Town:”\n value: Central Hospital\n input: select\n choices: [Central Hospital, Military Hospital, Port Hospital, St. Mark's Maternity Hospital (SMMH)]\n---\nWhen knitting (either via the ‘knit with parameters’ button or by render()), the pop-up window will have drop-down options to select from.\n\n\n\n\n\n\n\n\n\n\n\n\nParameterized example\nThe following code creates parameters for date and hospital, which are used in the R Markdown as params$date and params$hospital, respectively.\nIn the resulting report output, see how the data are filtered to the specific hospital, and the plot title refers to the correct hospital and date. We use the “linelist_cleaned.rds” file here, but it would be particularly appropriate if the linelist itself also had a datestamp within it to align with parameterised date.\n\n\n\n\n\n\n\n\n\nKnitting this produces the final output with the default font and layout.\n\n\n\n\n\n\n\n\n\n\n\nParameterisation without params\nIf you are rendering a R Markdown file with render() from a separate script, you can actually create the impact of parameterization without using the params: functionality.\nFor instance, in the R script that contains the render() command, you can simply define hospital and date as two R objects (values) before the render() command. In the R Markdown, you would not need to have a params: section in the YAML, and we would refer to the date object rather than params$date and hospital rather than params$hospital.\n\n# This is a R script that is separate from the R Markdown\n\n# define R objects\nhospital <- \"Central Hospital\"\ndate <- \"2021-04-10\"\n\n# Render the R markdown\nrmarkdown::render(input = \"create_output.Rmd\") \n\nFollowing this approach means means you can not “knit with parameters”, use the GUI, or include knitting options within the parameters. However it allows for simpler code, which may be advantageous.", + "text": "40.6 Parameterised reports\nYou can use parameterisation to make a report dynamic, such that it can be run with specific setting (e.g. a specific date or place or with certain knitting options). Below, we focus on the basics, but there is more detail online about parameterized reports.\nUsing the Ebola linelist as an example, let’s say we want to run a standard surveillance report for each hospital each day. We show how one can do this using parameters.\nImportant: dynamic reports are also possible without the formal parameter structure (without params:), using simple R objects in an adjacent R script. This is explained at the end of this section.\n\nSetting parameters\nYou have several options for specifying parameter values for your R Markdown output.\n\nOption 1: Set parameters within YAML\nEdit the YAML to include a params: option, with indented statements for each parameter you want to define. In this example we create parameters date and hospital, for which we specify values. These values are subject to change each time the report is run. If you use the “Knit” button to produce the output, the parameters will have these default values. Likewise, if you use render() the parameters will have these default values unless otherwise specified in the render() command.\n---\ntitle: Surveillance report\noutput: html_document\nparams:\n date: 2021-04-10\n hospital: Central Hospital\n---\nIn the background, these parameter values are contained within a read-only list called params. Thus, you can insert the parameter values in R code as you would another R object/value in your environment. Simply type params$ followed by the parameter name. For example params$hospital to represent the hospital name (“Central Hospital” by default).\nNote that parameters can also hold values true or false, and so these can be included in your knitr options for a R chunk. For example, you can set {r, eval=params$run} instead of {r, eval=FALSE}, and now whether the chunk runs or not depends on the value of a parameter run:.\nNote that for parameters that are dates, they will be input as a string. So for params$date to be interpreted in R code it will likely need to be wrapped with as.Date() or a similar function to convert to class Date.\n\n\nOption 2: Set parameters within render()\nAs mentioned above, as alternative to pressing the “Knit” button to produce the output is to execute the render() function from a separate script. In this later case, you can specify the parameters to be used in that rendering to the params = argument of render().\nNote than any parameter values provided here will overwrite their default values if written within the YAML. We write the values in quotation marks as in this case they should be defined as character/string values.\nThe below command renders “surveillance_report.Rmd”, specifies a dynamic output file name and folder, and provides a list() of two parameters and their values to the argument params =.\n\nrmarkdown::render(\n input = \"surveillance_report.Rmd\", \n output_file = stringr::str_glue(\"outputs/Report_{Sys.Date()}.docx\"),\n params = list(date = \"2021-04-10\", hospital = \"Central Hospital\"))\n\n\n\nOption 3: Set parameters using a Graphical User Interface\nFor a more interactive feel, you can also use the Graphical User Interface (GUI) to manually select values for parameters. To do this we can click the drop-down menu next to the ‘Knit’ button and choose ‘Knit with parameters’.\nA pop-up will appear allowing you to type in values for the parameters that are established in the document’s YAML.\n\n\n\n\n\n\n\n\n\nYou can achieve the same through a render() command by specifying params = \"ask\", as demonstrated below.\n\nrmarkdown::render(\n input = \"surveillance_report.Rmd\", \n output_file = stringr::str_glue(\"outputs/Report_{Sys.Date()}.docx\"),\n params = “ask”)\n\nHowever, typing values into this pop-up window is subject to error and spelling mistakes. You may prefer to add restrictions to the values that can be entered through drop-down menus. You can do this by adding in the YAML several specifications for each params: entry.\n\nlabel: is the title for that particular drop-down menu\nvalue: is the default (starting) value\ninput: set to select for drop-down menu\nchoices: provide the eligible values in the drop-down menu\n\nBelow, these specifications are written for the hospital parameter.\n---\ntitle: Surveillance report\noutput: html_document\nparams:\n date: 2021-04-10\n hospital: \n label: “Town:”\n value: Central Hospital\n input: select\n choices: [Central Hospital, Military Hospital, Port Hospital, St. Mark's Maternity Hospital (SMMH)]\n---\nWhen knitting (either via the ‘knit with parameters’ button or by render()), the pop-up window will have drop-down options to select from.\n\n\n\n\n\n\n\n\n\n\n\n\nParameterized example\nThe following code creates parameters for date and hospital, which are used in the R Markdown as params$date and params$hospital, respectively.\nIn the resulting report output, see how the data are filtered to the specific hospital, and the plot title refers to the correct hospital and date. We use the “linelist_cleaned.rds” file here, but it would be particularly appropriate if the linelist itself also had a datestamp within it to align with parameterised date.\n\n\n\n\n\n\n\n\n\nKnitting this produces the final output with the default font and layout.\n\n\n\n\n\n\n\n\n\n\n\nParameterisation without params\nIf you are rendering a R Markdown file with render() from a separate script, you can actually create the impact of parameterization without using the params: functionality.\nFor instance, in the R script that contains the render() command, you can simply define hospital and date as two R objects (values) before the render() command. In the R Markdown, you would not need to have a params: section in the YAML, and we would refer to the date object rather than params$date and hospital rather than params$hospital.\n\n# This is a R script that is separate from the R Markdown\n\n# define R objects\nhospital <- \"Central Hospital\"\ndate <- \"2021-04-10\"\n\n# Render the R markdown\nrmarkdown::render(input = \"create_output.Rmd\") \n\nFollowing this approach means means you can not “knit with parameters”, use the GUI, or include knitting options within the parameters. However it allows for simpler code, which may be advantageous.", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3596,7 +3596,7 @@ "href": "new_pages/rmarkdown.html#templates", "title": "40  Reports with R Markdown", "section": "40.8 Templates", - "text": "40.8 Templates\nBy using a template document that contains any desired formatting, you can adjust the aesthetics of how the Rmd output will look. You can create for instance an MS Word or Powerpoint file that contains pages/slides with the desired dimensions, watermarks, backgrounds, and fonts.\n\nWord documents\nTo create a template, start a new word document (or use an existing output with formatting the suits you), and edit fonts by defining the Styles. In Style,Headings 1, 2, and 3 refer to the various markdown header levels (# Header 1, ## Header 2 and ### Header 3 respectively). Right click on the style and click ‘modify’ to change the font formatting as well as the paragraph (e.g. you can introduce page breaks before certain styles which can help with spacing). Other aspects of the word document such as margins, page size, headers etc, can be changed like a usual word document you are working directly within.\n\n\n\n\n\n\n\n\n\n\n\nPowerpoint documents\nAs above, create a new slideset or use an existing powerpoint file with the desired formatting. For further editing, click on ‘View’ and ‘Slide Master’. From here you can change the ‘master’ slide appearance by editing the text formatting in the text boxes, as well as the background/page dimensions for the overall page.\n\n\n\n\n\n\n\n\n\nUnfortunately, editing powerpoint files is slightly less flexible:\n\nA first level header (# Header 1) will automatically become the title of a new slide,\nA ## Header 2 text will not come up as a subtitle but text within the slide’s main textbox (unless you find a way to maniuplate the Master view).\nOutputted plots and tables will automatically go into new slides. You will need to combine them, for instance the the patchwork function to combine ggplots, so that they show up on the same page. See this blog post about using the patchwork package to put multiple images on one slide.\n\nSee the officer package for a tool to work more in-depth with powerpoint presentations.\n\n\nIntegrating templates into the YAML\nOnce a template is prepared, the detail of this can be added in the YAML of the Rmd underneath the ‘output’ line and underneath where the document type is specified (which goes to a separate line itself). Note reference_doc can be used for powerpoint slide templates.\nIt is easiest to save the template in the same folder as where the Rmd file is (as in the example below), or in a subfolder within.\n---\ntitle: Surveillance report\noutput: \n word_document:\n reference_docx: \"template.docx\"\nparams:\n date: 2021-04-10\n hospital: Central Hospital\ntemplate:\n \n---\n\n\nFormatting HTML files\nHTML files do not use templates, but can have the styles configured within the YAML. HTMLs are interactive documents, and are particularly flexible. We cover some basic options here.\n\nTable of contents: We can add a table of contents with toc: true below, and also specify that it remains viewable (“floats”) as you scroll, with toc_float: true.\nThemes: We can refer to some pre-made themes, which come from a Bootswatch theme library. In the below example we use cerulean. Other options include: journal, flatly, darkly, readable, spacelab, united, cosmo, lumen, paper, sandstone, simplex, and yeti.\nHighlight: Configuring this changes the look of highlighted text (e.g. code within chunks that are shown). Supported styles include default, tango, pygments, kate, monochrome, espresso, zenburn, haddock, breezedark, and textmate.\n\nHere is an example of how to integrate the above options into the YAML.\n---\ntitle: \"HTML example\"\noutput:\n html_document:\n toc: true\n toc_float: true\n theme: cerulean\n highlight: kate\n \n---\nBelow are two examples of HTML outputs which both have floating tables of contents, but different theme and highlight styles selected:", + "text": "40.8 Templates\nBy using a template document that contains any desired formatting, you can adjust the aesthetics of how the Rmd output will look. You can create for instance an MS Word or Powerpoint file that contains pages/slides with the desired dimensions, watermarks, backgrounds, and fonts.\n\nWord documents\nTo create a template, start a new word document (or use an existing output with formatting the suits you), and edit fonts by defining the Styles. In Style,Headings 1, 2, and 3 refer to the various markdown header levels (# Header 1, ## Header 2 and ### Header 3 respectively). Right click on the style and click ‘modify’ to change the font formatting as well as the paragraph (e.g. you can introduce page breaks before certain styles which can help with spacing). Other aspects of the word document such as margins, page size, headers etc, can be changed like a usual word document you are working directly within.\n\n\n\n\n\n\n\n\n\n\n\nPowerpoint documents\nAs above, create a new slideset or use an existing powerpoint file with the desired formatting. For further editing, click on ‘View’ and ‘Slide Master’. From here you can change the ‘master’ slide appearance by editing the text formatting in the text boxes, as well as the background/page dimensions for the overall page.\n\n\n\n\n\n\n\n\n\nUnfortunately, editing powerpoint files is slightly less flexible:\n\nA first level header (# Header 1) will automatically become the title of a new slide,\nA ## Header 2 text will not come up as a subtitle but text within the slide’s main textbox (unless you find a way to maniuplate the Master view)\nOutputted plots and tables will automatically go into new slides. You will need to combine them, for instance the the patchwork function to combine ggplots, so that they show up on the same page. See this blog post about using the patchwork package to put multiple images on one slide\n\nSee the officer package for a tool to work more in-depth with powerpoint presentations.\n\n\nIntegrating templates into the YAML\nOnce a template is prepared, the detail of this can be added in the YAML of the Rmd underneath the ‘output’ line and underneath where the document type is specified (which goes to a separate line itself). Note reference_doc can be used for powerpoint slide templates.\nIt is easiest to save the template in the same folder as where the Rmd file is (as in the example below), or in a subfolder within.\n---\ntitle: Surveillance report\noutput: \n word_document:\n reference_docx: \"template.docx\"\nparams:\n date: 2021-04-10\n hospital: Central Hospital\ntemplate:\n \n---\n\n\nFormatting HTML files\nHTML files do not use templates, but can have the styles configured within the YAML. HTMLs are interactive documents, and are particularly flexible. We cover some basic options here.\n\nTable of contents: We can add a table of contents with toc: true below, and also specify that it remains viewable (“floats”) as you scroll, with toc_float: true.\nThemes: We can refer to some pre-made themes, which come from a Bootswatch theme library. In the below example we use cerulean. Other options include: journal, flatly, darkly, readable, spacelab, united, cosmo, lumen, paper, sandstone, simplex, and yeti. See here for a full list of freely available themes.\nHighlight: Configuring this changes the look of highlighted text (e.g. code within chunks that are shown). Supported styles include default, tango, pygments, kate, monochrome, espresso, zenburn, haddock, breezedark, and textmate.\n\nHere is an example of how to integrate the above options into the YAML.\n---\ntitle: \"HTML example\"\noutput:\n html_document:\n toc: true\n toc_float: true\n theme: cerulean\n highlight: kate\n \n---\nBelow are two examples of HTML outputs which both have floating tables of contents, but different theme and highlight styles selected:", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3607,7 +3607,7 @@ "href": "new_pages/rmarkdown.html#dynamic-content", "title": "40  Reports with R Markdown", "section": "40.9 Dynamic content", - "text": "40.9 Dynamic content\nIn an HTML output, your report content can be dynamic. Below are some examples:\n\nTables\nIn an HTML report, you can print data frame / tibbles such that the content is dynamic, with filters and scroll bars. There are several packages that offer this capability.\nTo do this with the DT package, as is used throughout this handbook, you can insert a code chunk like this:\n\n\n\n\n\n\n\n\n\nThe function datatable() will print the provided data frame as a dynamic table for the reader. You can set rownames = FALSE to simplify the far left-side of the table. filter = \"top\" provides a filter over each column. In the option() argument provide a list of other specifications. Below we include two: pageLength = 5 set the number of rows that appear as 5 (the remaining rows can be viewed by paging through arrows), and scrollX=TRUE enables a scrollbar on the bottom of the table (for columns that extend too far to the right).\nIf your dataset is very large, consider only showing the top X rows by wrapping the data frame in head().\n\n\nHTML widgets\nHTML widgets for R are a special class of R packages that enable increased interactivity by utilizing JavaScript libraries. You can embed them in HTML R Markdown outputs.\nSome common examples of these widgets include:\n\nPlotly (used in this handbook page and in the Interative plots page)\nvisNetwork (used in the Transmission Chains page of this handbook)\n\nLeaflet (used in the GIS Basics page of this handbook)\n\ndygraphs (useful for interactively showing time series data)\n\nDT (datatable()) (used to show dynamic tables with filter, sort, etc.)\n\nThe ggplotly() function from plotly is particularly easy to use. See the Interactive plots page.", + "text": "40.9 Dynamic content\nIn an HTML output, your report content can be dynamic. Below are some examples:\n\nTables\nIn an HTML report, you can print data frame / tibbles such that the content is dynamic, with filters and scroll bars. There are several packages that offer this capability.\nTo do this with the DT package, as is used throughout this handbook, you can insert a code chunk like this:\n\n\n\n\n\n\n\n\n\nThe function datatable() will print the provided data frame as a dynamic table for the reader. You can set rownames = FALSE to simplify the far left-side of the table. filter = \"top\" provides a filter over each column. In the option() argument provide a list of other specifications. Below we include two: pageLength = 5 set the number of rows that appear as 5 (the remaining rows can be viewed by paging through arrows), and scrollX=TRUE enables a scrollbar on the bottom of the table (for columns that extend too far to the right).\nIf your dataset is very large, consider only showing the top X rows by wrapping the data frame in head().\n\n\nHTML widgets\nHTML widgets for R are a special class of R packages that enable increased interactivity by utilizing JavaScript libraries. You can embed them in HTML R Markdown outputs.\nSome common examples of these widgets include:\n\nPlotly (used in this handbook page and in the Interative plots page)\nvisNetwork (used in the Transmission Chains page of this handbook)\n\nLeaflet (used in the GIS Basics page of this handbook)\ndygraphs (useful for interactively showing time series data)\nDT (datatable()) (used to show dynamic tables with filter, sort, etc.)\n\nThe ggplotly() function from plotly is particularly easy to use. See the Interactive plots page.", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3618,7 +3618,7 @@ "href": "new_pages/rmarkdown.html#resources", "title": "40  Reports with R Markdown", "section": "40.10 Resources", - "text": "40.10 Resources\nFurther information can be found via:\n\nhttps://bookdown.org/yihui/rmarkdown/\nhttps://rmarkdown.rstudio.com/articles_intro.html\n\nA good explainer of markdown vs knitr vs Rmarkdown is here: https://stackoverflow.com/questions/40563479/relationship-between-r-markdown-knitr-pandoc-and-bookdown", + "text": "40.10 Resources\nFurther information can be found via:\n\nR Markdown: The Definitive Guide\nIntroduction to R Markdown\n\nA good explainer of Markdown vs knitr vs R Markdown can be found here.", "crumbs": [ "Reports and dashboards", "40  Reports with R Markdown" @@ -3761,7 +3761,7 @@ "href": "new_pages/flexdashboard.html#section-attributes", "title": "42  Dashboards with R Markdown", "section": "42.4 Section attributes", - "text": "42.4 Section attributes\nAs in a normal R markdown, you can specify attributes to apply to parts of your dashboard by including key=value options after a heading, within curly brackets { }. For example, in a typical HTML R Markdown report you might organize sub-headings into tabs with ## My heading {.tabset}.\nNote that these attributes are written after a heading in a text portion of the script. These are different than the knitr options inserted within at the top of R code chunks, such as out.height =.\nSection attributes specific to flexdashboard include:\n\n{data-orientation=} Set to either rows or columns. If your dashboard has multiple pages, add this attribute to each page to indicate orientation (further explained in layout section).\n\n{data-width=} and {data-height=} set relative size of charts, columns, rows laid out in the same dimension (horizontal or vertical). Absolute sizes are adjusted to best fill the space on any display device thanks to the flexbox engine.\n\nHeight of charts also depends on whether you set the YAML parameter vertical_layout: fill or vertical_layout: scroll. If set to scroll, figure height will reflect the traditional fig.height = option in the R code chunk.\n\nSee complete size documentation at the flexdashboard website\n\n\n{.hidden} Use this to exclude a specific page from the navigation bar\n\n{data-navbar=} Use this in a page-level heading to nest it within a navigation bar drop-down menu. Provide the name (in quotes) of the drop-down menu. See example below.", + "text": "42.4 Section attributes\nAs in a normal R markdown, you can specify attributes to apply to parts of your dashboard by including key=value options after a heading, within curly brackets { }. For example, in a typical HTML R Markdown report you might organize sub-headings into tabs with ## My heading {.tabset}.\nNote that these attributes are written after a heading in a text portion of the script. These are different than the knitr options inserted within at the top of R code chunks, such as out.height =.\nSection attributes specific to flexdashboard include:\n\n{data-orientation=} Set to either rows or columns. If your dashboard has multiple pages, add this attribute to each page to indicate orientation (further explained in layout section).\n\n{data-width=} and {data-height=} set relative size of charts, columns, rows laid out in the same dimension (horizontal or vertical). Absolute sizes are adjusted to best fill the space on any display device thanks to the flexbox engine.\n\nHeight of charts also depends on whether you set the YAML parameter vertical_layout: fill or vertical_layout: scroll. If set to scroll, figure height will reflect the traditional fig.height = option in the R code chunk.\n\nSee complete size documentation at the flexdashboard website\n\n\n{.hidden} Use this to exclude a specific page from the navigation bar.\n\n{data-navbar=} Use this in a page-level heading to nest it within a navigation bar drop-down menu. Provide the name (in quotes) of the drop-down menu. See example below.", "crumbs": [ "Reports and dashboards", "42  Dashboards with R Markdown" @@ -3772,7 +3772,7 @@ "href": "new_pages/flexdashboard.html#layout", "title": "42  Dashboards with R Markdown", "section": "42.5 Layout", - "text": "42.5 Layout\nAdjust the layout of your dashboard in the following ways:\n\nAdd pages, columns/rows, and charts with R Markdown headings (e.g. #, ##, or ###)\n\nAdjust the YAML parameter orientation: to either rows or columns\n\nSpecify whether the layout fills the browser or allows scrolling\n\nAdd tabs to a particular section heading\n\n\nPages\nFirst-level headings (#) in the R Markdown will represent “pages” of the dashboard. By default, pages will appear in a navigation bar along the top of the dashboard.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYou can group pages into a “menu” within the top navigation bar by adding the attribute {data-navmenu=} to the page heading. Be careful - do not include spaces around the equals sign otherwise it will not work!\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nYou can also convert a page or a column into a “sidebar” on the left side of the dashboard by adding the {.sidebar} attribute. It can hold text (viewable from any page), or if you have integrated shiny interactivity it can be useful to hold user-input controls such as sliders or drop-down menus.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\n\n\nOrientation\nSet the orientation: yaml parameter to indicate how your second-level (##) R Markdown headings should be interpreted - as either orientation: columns or orientation: rows.\nSecond-level headings (##) will be interpreted as new columns or rows based on this orientation setting.\nIf you set orientation: columns, second-level headers will create new columns in the dashboard. The below dashboard has one page, containing two columns, with a total of three panels. You can adjust the relative width of the columns with {data-width=} as shown below.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nIf you set orientation: rows, second-level headers will create new rows instead of columns. Below is the same script as above, but orientation: rows so that second-level headings produce rows instead of columns. You can adjust the relative height of the rows with {data-height=} as shown below.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nIf your dashboard has multiple pages, you can designate the orientation for each specific page by adding the {data-orientation=} attribute the header of each page (specify either rows or columns without quotes).\n\n\nTabs\nYou can divide content into tabs with the {.tabset} attribute, as in other HTML R Markdown outputs.\nSimply add this attribute after the desired heading. Sub-headings under that heading will be displayed as tabs. For example, in the example script below column 2 on the right (##) is modified so that the epidemic curve and table panes (###) are displayed in tabs.\nYou can do the same with rows if your orientation is rows.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:", + "text": "42.5 Layout\nAdjust the layout of your dashboard in the following ways:\n\nAdd pages, columns/rows, and charts with R Markdown headings (e.g. #, ##, or ###).\n\nAdjust the YAML parameter orientation: to either rows or columns.\n\nSpecify whether the layout fills the browser or allows scrolling.\n\nAdd tabs to a particular section heading.\n\n\nPages\nFirst-level headings (#) in the R Markdown will represent “pages” of the dashboard. By default, pages will appear in a navigation bar along the top of the dashboard.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYou can group pages into a “menu” within the top navigation bar by adding the attribute {data-navmenu=} to the page heading. Be careful - do not include spaces around the equals sign otherwise it will not work!\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nYou can also convert a page or a column into a “sidebar” on the left side of the dashboard by adding the {.sidebar} attribute. It can hold text (viewable from any page), or if you have integrated shiny interactivity it can be useful to hold user-input controls such as sliders or drop-down menus.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\n\n\nOrientation\nSet the orientation: yaml parameter to indicate how your second-level (##) R Markdown headings should be interpreted - as either orientation: columns or orientation: rows.\nSecond-level headings (##) will be interpreted as new columns or rows based on this orientation setting.\nIf you set orientation: columns, second-level headers will create new columns in the dashboard. The below dashboard has one page, containing two columns, with a total of three panels. You can adjust the relative width of the columns with {data-width=} as shown below.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nIf you set orientation: rows, second-level headers will create new rows instead of columns. Below is the same script as above, but orientation: rows so that second-level headings produce rows instead of columns. You can adjust the relative height of the rows with {data-height=} as shown below.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nIf your dashboard has multiple pages, you can designate the orientation for each specific page by adding the {data-orientation=} attribute the header of each page (specify either rows or columns without quotes).\n\n\nTabs\nYou can divide content into tabs with the {.tabset} attribute, as in other HTML R Markdown outputs.\nSimply add this attribute after the desired heading. Sub-headings under that heading will be displayed as tabs. For example, in the example script below column 2 on the right (##) is modified so that the epidemic curve and table panes (###) are displayed in tabs.\nYou can do the same with rows if your orientation is rows.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:", "crumbs": [ "Reports and dashboards", "42  Dashboards with R Markdown" @@ -3783,7 +3783,7 @@ "href": "new_pages/flexdashboard.html#adding-content", "title": "42  Dashboards with R Markdown", "section": "42.6 Adding content", - "text": "42.6 Adding content\nLet’s begin to build a dashboard. Our simple dashboard will have 1 page, 2 columns, and 4 panels. We will build the panels piece-by-piece for demonstration.\nYou can easily include standard R outputs such as text, ggplots, and tables (see Tables for presentation page). Simply code them within an R code chunk as you would for any other R Markdown script.\nNote: you can download the finished Rmd script and HTML dashboard output - see the Download handbook and data page.\n\nText\nYou can type in Markdown text and include in-line code as for any other R Markdown output. See the Reports with R Markdown page for details.\nIn this dashboard we include a summary text panel that includes dynamic text showing the latest hospitalisation date and number of cases reported in the outbreak.\n\n\nTables\nYou can include R code chunks that print outputs such as tables. But the output will look best and respond to the window size if you use the kable() function from knitr to display your tables. The flextable functions may produce tables that are shortened / cut-off.\nFor example, below we feed the linelist() through a count() command to produce a summary table of cases by hospital. Ultimately, the table is piped to knitr::kable() and the result has a scroll bar on the right. You can read more about customizing your table with kable() and kableExtra here.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nIf you want to show a dynamic table that allows the user to filter, sort, and/or click through “pages” of the data frame, use the package DT and it’s function datatable(), as in the code below.\nThe example code below, the data frame linelist is printed. You can set rownames = FALSE to conserve horizontal space, and filter = \"top\" to have filters on top of every column. A list of other specifications can be provided to options =. Below, we set pageLength = so that 5 rows appear and scrollX = so the user can use a scroll bar on the bottom to scroll horizontally. The argument class = 'white-space: nowrap' ensures that each row is only one line (not multiple lines). You can read about other possible arguments and values here or by entering ?datatable\n\nDT::datatable(linelist, \n rownames = FALSE, \n options = list(pageLength = 5, scrollX = TRUE), \n class = 'white-space: nowrap' )\n\n\n\nPlots\nYou can print plots to a dashboard pane as you would in an R script. In our example, we use the incidence2 package to create an “epicurve” by age group with two simple commands (see Epidemic curves page). However, you could use ggplot() and print a plot in the same manner.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\n\n\nInteractive plots\nYou can also pass a standard ggplot or other plot object to ggplotly() from the plotly package (see the Interactive plots page). This will make your plot interactive, allow the reader to “zoom in”, and show-on-hover the value of every data point (in this scenario the number of cases per week and age group in the curve).\n\nage_outbreak <- incidence(linelist, date_onset, \"week\", groups = age_cat)\nplot(age_outbreak, fill = age_cat, col_pal = muted, title = \"\") %>% \n plotly::ggplotly()\n\nHere is what this looks like in the dashboard (gif). This interactive functionality will still work even if you email the dashboard as a static file (not online on a server).\n\n\n\n\n\n\n\n\n\n\n\nHTML widgets\nHTML widgets for R are a special class of R packages that increases interactivity by utilizing JavaScript libraries. You can embed them in R Markdown outputs (such as a flexdashboard) and in Shiny dashboards.\nSome common examples of these widgets include:\n\nPlotly (used in this handbook page and in the Interative plots page)\nvisNetwork (used in the Transmission Chains page of this handbook)\n\nLeaflet (used in the GIS Basics page of this handbook)\n\ndygraphs (useful for interactively showing time series data)\n\nDT (datatable()) (used to show dynamic tables with filter, sort, etc.)\n\nBelow we demonstrate adding an epidemic transmission chain which uses visNetwork to the dashboard. The script shows only the new code added to the “Column 2” section of the R Markdown script. You can find the code in the Transmission chains page of this handbook.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:", + "text": "42.6 Adding content\nLet’s begin to build a dashboard. Our simple dashboard will have 1 page, 2 columns, and 4 panels. We will build the panels piece-by-piece for demonstration.\nYou can easily include standard R outputs such as text, ggplots, and tables (see Tables for presentation page). Simply code them within an R code chunk as you would for any other R Markdown script.\nNote: you can download the finished Rmd script and HTML dashboard output - see the Download handbook and data page.\n\nText\nYou can type in Markdown text and include in-line code as for any other R Markdown output. See the Reports with R Markdown page for details.\nIn this dashboard we include a summary text panel that includes dynamic text showing the latest hospitalisation date and number of cases reported in the outbreak.\n\n\nTables\nYou can include R code chunks that print outputs such as tables. But the output will look best and respond to the window size if you use the kable() function from knitr to display your tables. The flextable functions may produce tables that are shortened / cut-off.\nFor example, below we feed the linelist() through a count() command to produce a summary table of cases by hospital. Ultimately, the table is piped to knitr::kable() and the result has a scroll bar on the right. You can read more about customizing your table with kable() and kableExtra here.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\nIf you want to show a dynamic table that allows the user to filter, sort, and/or click through “pages” of the data frame, use the package DT and it’s function datatable(), as in the code below.\nThe example code below, the data frame linelist is printed. You can set rownames = FALSE to conserve horizontal space, and filter = \"top\" to have filters on top of every column. A list of other specifications can be provided to options =. Below, we set pageLength = so that 5 rows appear and scrollX = so the user can use a scroll bar on the bottom to scroll horizontally. The argument class = 'white-space: nowrap' ensures that each row is only one line (not multiple lines). You can read about other possible arguments and values here or by entering ?datatable\n\nDT::datatable(linelist, \n rownames = FALSE, \n options = list(pageLength = 5, scrollX = TRUE), \n class = 'white-space: nowrap' )\n\n\n\nPlots\nYou can print plots to a dashboard pane as you would in an R script. In our example, we use the incidence2 package to create an “epicurve” by age group with two simple commands (see An Introduction to incidence2 page). However, you could use ggplot() and print a plot in the same manner.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:\n\n\n\n\n\n\n\n\n\n\n\nInteractive plots\nYou can also pass a standard ggplot or other plot object to ggplotly() from the plotly package (see the Interactive plots page). This will make your plot interactive, allow the reader to “zoom in”, and show-on-hover the value of every data point (in this scenario the number of cases per week and age group in the curve).\n\nage_outbreak <- incidence(linelist, date_onset, \"week\", groups = age_cat)\nplot(age_outbreak, fill = age_cat, col_pal = muted, title = \"\") %>% \n plotly::ggplotly()\n\nHere is what this looks like in the dashboard (gif). This interactive functionality will still work even if you email the dashboard as a static file (not online on a server).\n\n\n\n\n\n\n\n\n\n\n\nHTML widgets\nHTML widgets for R are a special class of R packages that increases interactivity by utilizing JavaScript libraries. You can embed them in R Markdown outputs (such as a flexdashboard) and in Shiny dashboards.\nSome common examples of these widgets include:\n\nPlotly (used in this handbook page and in the Interative plots page).\nvisNetwork (used in the Transmission Chains page of this handbook).\nLeaflet (used in the GIS Basics page of this handbook).\n\ndygraphs (useful for interactively showing time series data).\n\nDT (datatable()) (used to show dynamic tables with filter, sort, etc.).\n\nBelow we demonstrate adding an epidemic transmission chain which uses visNetwork to the dashboard. The script shows only the new code added to the “Column 2” section of the R Markdown script. You can find the code in the Transmission chains page of this handbook.\n\n\n\n\n\n\n\n\n\nHere is what the script produces:", "crumbs": [ "Reports and dashboards", "42  Dashboards with R Markdown" @@ -3805,7 +3805,7 @@ "href": "new_pages/flexdashboard.html#shiny", "title": "42  Dashboards with R Markdown", "section": "42.8 Shiny", - "text": "42.8 Shiny\nIntegrating the R package shiny can make your dashboards even more reactive to user input. For example, you could have the user select a jurisdiction, or a date range, and have panels react to their choice (e.g. filter the data displayed). To embed shiny reactivity into flexdashboard, you need only make a few changes to your flexdashboard R Markdown script.\nYou can use shiny to produce apps/dashboards without flexdashboard too. The handbook page on Dashboards with Shiny gives an overview of this approach, including primers on shiny syntax, app file structure, and options for sharing/publishing (including free server options). These syntax and general tips translate into the flexdashboard context as well.\nEmbedding shiny in flexdashboard is however, a fundamental change to your flexdashboard. It will no longer produce an HTML output that you can send by email and anyone could open and view. Instead, it will be an “app”. The “Knit” button at the top of the script will be replaced by a “Run document” icon, which will open an instance of the interactive the dashboard locally on your computer.\nSharing your dashboard will now require that you either:\n\nSend the Rmd script to the viewer, they open it in R on their computer, and run the app, or\n\nThe app/dashboard is hosted on a server accessible to the viewer\n\nThus, there are benefits to integrating shiny, but also complications. If easy sharing by email is a priority and you don’t need shiny reactive capabilities, consider the reduced interactivity offered by ggplotly() as demonstrated above.\nBelow we give a very simple example using the same “outbreak_dashboard.Rmd” as above. Extensive documentation on integrating Shiny into flexdashboard is available online here.\n\nSettings\nEnable shiny in a flexdashboard by adding the YAML parameter runtime: shiny at the same indentation level as output:, as below:\n---\ntitle: \"Outbreak dashboard (Shiny demo)\"\noutput: \n flexdashboard::flex_dashboard:\n orientation: columns\n vertical_layout: fill\nruntime: shiny\n---\nIt is also convenient to enable a “side bar” to hold the shiny input widgets that will collect information from the user. As explained above, create a column and indicate the {.sidebar} option to create a side bar on the left side. You can add text and R chunks containing the shiny input commands within this column.\nIf your app/dashboard is hosted on a server and may have multiple simultaneous users, name the first R code chunk as global. Include the commands to import/load your data in this chunk. This special named chunk is treated differently, and the data imported within it are only imported once (not continuously) and are available for all users. This improves the start-up speed of the app.\n\n\nWorked example\nHere we adapt the flexdashboard script “outbreak_dashboard.Rmd” to include shiny. We will add the capability for the user to select a hospital from a drop-down menu, and have the epidemic curve reflect only cases from that hospital, with a dynamic plot title. We do the following:\n\nAdd runtime: shiny to the YAML\n\nRe-name the setup chunk as global\n\nCreate a sidebar containing:\n\nCode to create a vector of unique hospital names\n\nA selectInput() command (shiny drop-down menu) with the choice of hospital names. The selection is saved as hospital_choice, which can be referenced in later code as input$hospital_choice\n\n\nThe epidemic curve code (column 2) is wrapped within renderPlot({ }), including:\n\nA filter on the dataset restricting the column hospital to the current value of input$hospital_choice\n\nA dynamic plot title that incorporates input$hospital_choice\n\n\nNote that any code referencing an input$ value must be within a render({}) function (to be reactive).\nHere is the top of the script, including YAML, global chunk, and sidebar:\n\n\n\n\n\n\n\n\n\nHere is the Column 2, with the reactive epicurve plot:\n\n\n\n\n\n\n\n\n\nAnd here is the dashboard:\n\n\n\n\n\n\n\n\n\n\n\nOther examples\nTo read a health-related example of a Shiny-flexdashboard using the shiny interactivity and the leaflet mapping widget, see this chapter of the online book Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny.", + "text": "42.8 Shiny\nIntegrating the R package shiny can make your dashboards even more reactive to user input. For example, you could have the user select a jurisdiction, or a date range, and have panels react to their choice (e.g. filter the data displayed). To embed shiny reactivity into flexdashboard, you need only make a few changes to your flexdashboard R Markdown script.\nYou can use shiny to produce apps/dashboards without flexdashboard too. The handbook page on Dashboards with Shiny gives an overview of this approach, including primers on shiny syntax, app file structure, and options for sharing/publishing (including free server options). These syntax and general tips translate into the flexdashboard context as well.\nEmbedding shiny in flexdashboard is however, a fundamental change to your flexdashboard. It will no longer produce an HTML output that you can send by email and anyone could open and view. Instead, it will be an “app”. The “Knit” button at the top of the script will be replaced by a “Run document” icon, which will open an instance of the interactive the dashboard locally on your computer.\nSharing your dashboard will now require that you either:\n\nSend the Rmd script to the viewer, they open it in R on their computer, and run the app, or\n\nthe app/dashboard is hosted on a server accessible to the viewer.\n\nThus, there are benefits to integrating shiny, but also complications. If easy sharing by email is a priority and you don’t need shiny reactive capabilities, consider the reduced interactivity offered by ggplotly() as demonstrated above.\nBelow we give a very simple example using the same “outbreak_dashboard.Rmd” as above. Extensive documentation on integrating Shiny into flexdashboard is available online here.\n\nSettings\nEnable shiny in a flexdashboard by adding the YAML parameter runtime: shiny at the same indentation level as output:, as below:\n---\ntitle: \"Outbreak dashboard (Shiny demo)\"\noutput: \n flexdashboard::flex_dashboard:\n orientation: columns\n vertical_layout: fill\nruntime: shiny\n---\nIt is also convenient to enable a “side bar” to hold the shiny input widgets that will collect information from the user. As explained above, create a column and indicate the {.sidebar} option to create a side bar on the left side. You can add text and R chunks containing the shiny input commands within this column.\nIf your app/dashboard is hosted on a server and may have multiple simultaneous users, name the first R code chunk as global. Include the commands to import/load your data in this chunk. This special named chunk is treated differently, and the data imported within it are only imported once (not continuously) and are available for all users. This improves the start-up speed of the app.\n\n\nWorked example\nHere we adapt the flexdashboard script “outbreak_dashboard.Rmd” to include shiny. We will add the capability for the user to select a hospital from a drop-down menu, and have the epidemic curve reflect only cases from that hospital, with a dynamic plot title. We do the following:\n\nAdd runtime: shiny to the YAML.\n\nRe-name the setup chunk as global.\n\nCreate a sidebar containing:\n\nCode to create a vector of unique hospital names.\n\nA selectInput() command (shiny drop-down menu) with the choice of hospital names. The selection is saved as hospital_choice, which can be referenced in later code as input$hospital_choice.\n\n\nThe epidemic curve code (column 2) is wrapped within renderPlot({ }), including:\n\nA filter on the dataset restricting the column hospital to the current value of input$hospital_choice.\n\nA dynamic plot title that incorporates input$hospital_choice.\n\n\nNote that any code referencing an input$ value must be within a render({}) function (to be reactive).\nHere is the top of the script, including YAML, global chunk, and sidebar:\n\n\n\n\n\n\n\n\n\nHere is the Column 2, with the reactive epicurve plot:\n\n\n\n\n\n\n\n\n\nAnd here is the dashboard:\n\n\n\n\n\n\n\n\n\n\n\nOther examples\nTo read a health-related example of a Shiny-flexdashboard using the shiny interactivity and the leaflet mapping widget, see this chapter of the online book Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny.", "crumbs": [ "Reports and dashboards", "42  Dashboards with R Markdown" @@ -3827,7 +3827,7 @@ "href": "new_pages/flexdashboard.html#resources", "title": "42  Dashboards with R Markdown", "section": "42.10 Resources", - "text": "42.10 Resources\nExcellent tutorials that informed this page can be found below. If you review these, most likely within an hour you can have your own dashboard.\nhttps://bookdown.org/yihui/rmarkdown/dashboards.html\nhttps://rmarkdown.rstudio.com/flexdashboard/\nhttps://rmarkdown.rstudio.com/flexdashboard/using.html\nhttps://rmarkdown.rstudio.com/flexdashboard/examples.html", + "text": "42.10 Resources\nExcellent tutorials that informed this page can be found below. If you review these, most likely within an hour you can have your own dashboard.\nRmarkdown and Dashboards\nFlexdashboard\nFlexdashboard gallery", "crumbs": [ "Reports and dashboards", "42  Dashboards with R Markdown" @@ -3860,7 +3860,7 @@ "href": "new_pages/shiny_basics.html#the-structure-of-a-shiny-app", "title": "43  Dashboards with Shiny", "section": "43.2 The structure of a shiny app", - "text": "43.2 The structure of a shiny app\n\nBasic file structures\nTo understand shiny, we first need to understand how the file structure of an app works! We should make a brand new directory before we start. This can actually be made easier by choosing New project in Rstudio, and choosing Shiny Web Application. This will create the basic structure of a shiny app for you.\nWhen opening this project, you’ll notice there is a .R file already present called app.R. It is essential that we have one of two basic file structures:\n\nOne file called app.R, or\n\nTwo files, one called ui.R and the other server.R\n\nIn this page, we will use the first approach of having one file called app.R. Here is an example script:\n\n# an example of app.R\n\nlibrary(shiny)\n\nui <- fluidPage(\n\n # Application title\n titlePanel(\"My app\"),\n\n # Sidebar with a slider input widget\n sidebarLayout(\n sidebarPanel(\n sliderInput(\"input_1\")\n ),\n\n # Show a plot \n mainPanel(\n plotOutput(\"my_plot\")\n )\n )\n)\n\n# Define server logic required to draw a histogram\nserver <- function(input, output) {\n \n plot_1 <- reactive({\n plot_func(param = input_1)\n })\n \n output$my_plot <- renderPlot({\n plot_1()\n })\n}\n\n\n# Run the application \nshinyApp(ui = ui, server = server)\n\nIf you open this file, you’ll notice that two objects are defined - one called ui and another called server. These objects must be defined in every shiny app and are central to the structure of the app itself! In fact, the only difference between the two file structures described above is that in structure 1, both ui and server are defined in one file, whereas in structure 2 they are defined in separate files. Note: we can also (and we should if we have a larger app) have other .R files in our structure that we can source() into our app.\n\n\nThe server and the ui\nWe next need to understand what the server and ui objects actually do. Put simply, these are two objects that are interacting with each other whenever the user interacts with the shiny app.\nThe UI element of a shiny app is, on a basic level, R code that creates an HTML interface. This means everything that is displayed in the UI of an app. This generally includes:\n\n“Widgets” - dropdown menus, check boxes, sliders, etc that can be interacted with by the user\nPlots, tables, etc - outputs that are generated with R code\nNavigation aspects of an app - tabs, panes, etc.\nGeneric text, hyperlinks, etc\nHTML and CSS elements (addressed later)\n\nThe most important thing to understand about the UI is that it receives inputs from the user and displays outputs from the server. There is no active code running in the ui at any time - all changes seen in the UI are passed through the server (more or less). So we have to make our plots, downloads, etc in the server\nThe server of the shiny app is where all code is being run once the app starts up. The way this works is a little confusing. The server function will effectively react to the user interfacing with the UI, and run chunks of code in response. If things change in the server, these will be passed back up to the ui, where the changes can be seen. Importantly, the code in the server will be executed non-consecutively (or it’s best to think of it this way). Basically, whenever a ui input affects a chunk of code in the server, it will run automatically, and that output will be produced and displayed.\nThis all probably sounds very abstract for now, so we’ll have to dive into some examples to get a clear idea of how this actually works.\n\n\nBefore you start to build an app\nBefore you begin to build an app, its immensely helpful to know what you want to build. Since your UI will be written in code, you can’t really visualise what you’re building unless you are aiming for something specific. For this reason, it is immensely helpful to look at lots of examples of shiny apps to get an idea of what you can make - even better if you can look at the source code behind these apps! Some great resources for this are:\n\nThe Rstudio app gallery\n\nOnce you get an idea for what is possible, it’s also helpful to map out what you want yours to look like - you can do this on paper or in any drawing software (PowerPoint, MS paint, etc.). It’s helpful to start simple for your first app! There’s also no shame in using code you find online of a nice app as a template for your work - its much easier than building something from scratch!", + "text": "43.2 The structure of a shiny app\n\nBasic file structures\nTo understand shiny, we first need to understand how the file structure of an app works! We should make a brand new directory before we start. This can actually be made easier by choosing New project in Rstudio, and choosing Shiny Web Application. This will create the basic structure of a shiny app for you.\nWhen opening this project, you’ll notice there is a .R file already present called app.R. It is essential that we have one of two basic file structures:\n\nOne file called app.R, or\n\nTwo files, one called ui.R and the other server.R\n\nIn this page, we will use the first approach of having one file called app.R. Here is an example script:\n\n# an example of app.R\n\nlibrary(shiny)\n\nui <- fluidPage(\n \n # Application title\n titlePanel(\"My app\"),\n \n # Sidebar with a slider input widget\n sidebarLayout(\n sidebarPanel(\n sliderInput(inputId = \"input_1\",\n label = \"Slider\",\n min = 1,\n max = 50,\n value = 1)\n ),\n \n # Show a plot \n mainPanel(\n plotOutput(\"my_plot\")\n )\n )\n)\n\n# Define server logic required to draw a histogram\nserver <- function(input, output) {\n \n plot_1 <- reactive({\n plot_func(param = input_1)\n })\n \n output$my_plot <- renderPlot({\n plot_1()\n })\n}\n\n\n# Run the application \nshinyApp(ui = ui, server = server)\n\nIf you open this file, you’ll notice that two objects are defined - one called ui and another called server. These objects must be defined in every shiny app and are central to the structure of the app itself! In fact, the only difference between the two file structures described above is that in structure 1, both ui and server are defined in one file, whereas in structure 2 they are defined in separate files. Note: we can also (and we should if we have a larger app) have other .R files in our structure that we can source() into our app.\n\n\nThe server and the ui\nWe next need to understand what the server and ui objects actually do. Put simply, these are two objects that are interacting with each other whenever the user interacts with the shiny app.\nThe UI element of a shiny app is, on a basic level, R code that creates an HTML interface. This means everything that is displayed in the UI of an app. This generally includes:\n\n“Widgets” - dropdown menus, check boxes, sliders, etc that can be interacted with by the user.\nPlots, tables, etc - outputs that are generated with R code.\nNavigation aspects of an app - tabs, panes, etc.\nGeneric text, hyperlinks, etc.\nHTML and CSS elements (addressed later).\n\nThe most important thing to understand about the UI is that it receives inputs from the user and displays outputs from the server. There is no active code running in the ui at any time - all changes seen in the UI are passed through the server (more or less). So we have to make our plots, downloads, etc in the server\nThe server of the shiny app is where all code is being run once the app starts up. The way this works is a little confusing. The server function will effectively react to the user interfacing with the UI, and run chunks of code in response. If things change in the server, these will be passed back up to the ui, where the changes can be seen. Importantly, the code in the server will be executed non-consecutively (or it’s best to think of it this way). Basically, whenever a ui input affects a chunk of code in the server, it will run automatically, and that output will be produced and displayed.\nThis all probably sounds very abstract for now, so we’ll have to dive into some examples to get a clear idea of how this actually works.\n\n\nBefore you start to build an app\nBefore you begin to build an app, its immensely helpful to know what you want to build. Since your UI will be written in code, you can’t really visualise what you’re building unless you are aiming for something specific. For this reason, it is immensely helpful to look at lots of examples of shiny apps to get an idea of what you can make - even better if you can look at the source code behind these apps! Some great resources for this are:\n\nRstudio app gallery\nAppsilon R Shiny Demo Gallery\n\nOnce you get an idea for what is possible, it’s also helpful to map out what you want yours to look like - you can do this on paper or in any drawing software (PowerPoint, MS paint, etc.). It’s helpful to start simple for your first app! There’s also no shame in using code you find online of a nice app as a template for your work - its much easier and more efficient than building something from scratch!", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3871,7 +3871,7 @@ "href": "new_pages/shiny_basics.html#building-a-ui", "title": "43  Dashboards with Shiny", "section": "43.3 Building a UI", - "text": "43.3 Building a UI\nWhen building our app, its easier to work on the UI first so we can see what we’re making, and not risk the app failing because of any server errors. As mentioned previously, its often good to use a template when working on the UI. There are a number of standard layouts that can be used with shiny that are available from the base shiny package, but it’s worth noting that there are also a number of package extensions such as shinydashboard. We’ll use an example from base shiny to start with.\nA shiny UI is generally defined as a series of nested functions, in the following order\n\nA function defining the general layout (the most basic is fluidPage(), but more are available)\nPanels within the layout such as:\n\na sidebar (sidebarPanel())\na “main” panel (mainPanel())\na tab (tabPanel())\na generic “column” (column())\n\nWidgets and outputs - these can confer inputs to the server (widgets) or outputs from the server (outputs)\n\nWidgets generally are styled as xxxInput() e.g. selectInput()\nOutputs are generally styled as xxxOutput() e.g. plotOutput()\n\n\nIt’s worth stating again that these can’t be visualised easily in an abstract way, so it’s best to look at an example! Lets consider making a basic app that visualises our malaria facility count data by district. This data has a lot of differnet parameters, so it would be great if the end user could apply some filters to see the data by age group/district as they see fit! We can use a very simple shiny layout to start - the sidebar layout. This is a layout where widgets are placed in a sidebar on the left, and the plot is placed on the right.\nLets plan our app - we can start with a selector that lets us choose the district where we want to visualise data, and another to let us visualise the age group we are interested in. We’ll aim to use these filters to show an epicurve that reflects these parameters. So for this we need:\n\nTwo dropdown menus that let us choose the district we want, and the age group we’re interested in.\nAn area where we can show our resulting epicurve.\n\nThis might look something like this:\n\nlibrary(shiny)\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = TRUE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\")\n )\n \n )\n)\n\nWhen app.R is run with the above UI code (with no active code in the server portion of app.R) the layout appears looking like this - note that there will be no plot if there is no server to render it, but our inputs are working!\n\n\n\n\n\n\n\n\n\nThis is a good opportunity to discuss how widgets work - note that each widget is accepting an inputId, a label, and a series of other options that are specific to the widget type. This inputId is extremely important - these are the IDs that are used to pass information from the UI to the server. For this reason, they must be unique. You should make an effort to name them something sensible, and specific to what they are interacting with in cases of larger apps.\nYou should read documentation carefully for full details on what each of these widgets do. Widgets will pass specific types of data to the server depending on the widget type, and this needs to be fully understood. For example, selectInput() will pass a character type to the server:\n\nIf we select Spring for the first widget here, it will pass the character object \"Spring\" to the server.\nIf we select two items from the dropdown menu, they will come through as a character vector (e.g. c(\"Spring\", \"Bolo\")).\n\nOther widgets will pass different types of object to the server! For example:\n\nnumericInput() will pass a numeric type object to the server\ncheckboxInput() will pass a logical type object to the server (TRUE or FALSE)\n\nIt’s also worth noting the named vector we used for the age data here. For many widgets, using a named vector as the choices will display the names of the vector as the display choices, but pass the selected value from the vector to the server. I.e. here someone can select “15+” from the drop-down menu, and the UI will pass \"malaria_rdt_15\" to the server - which happens to be the name of the column we’re interested in!\nThere are loads of widgets that you can use to do lots of things with your app. Widgets also allow you to upload files into your app, and download outputs. There are also some excellent shiny extensions that give you access to more widgets than base shiny - the shinyWidgets package is a great example of this. To look at some examples you can look at the following links:\n\nbase shiny widget gallery\nshinyWidgets gallery", + "text": "43.3 Building a UI\nWhen building our app, its easier to work on the UI first so we can see what we’re making, and not risk the app failing because of any server errors. As mentioned previously, its often good to use a template when working on the UI. There are a number of standard layouts that can be used with shiny that are available from the base shiny package, but it’s worth noting that there are also a number of package extensions such as shinydashboard. We’ll use an example from base shiny to start with.\nA shiny UI is generally defined as a series of nested functions, in the following order:\n\nA function defining the general layout (the most basic is fluidPage(), but more are available).\nPanels within the layout such as:\n\na sidebar (sidebarPanel()).\na “main” panel (mainPanel()).\na tab (tabPanel()).\na generic “column” (column()).\n\nWidgets and outputs - these can confer inputs to the server (widgets) or outputs from the server (outputs).\n\nWidgets generally are styled as xxxInput() e.g. selectInput().\nOutputs are generally styled as xxxOutput() e.g. plotOutput().\n\n\nIt’s worth stating again that these can’t be visualised easily in an abstract way, so it’s best to look at an example! Lets consider making a basic app that visualises our malaria facility count data by district. This data has a lot of differnet parameters, so it would be great if the end user could apply some filters to see the data by age group/district as they see fit! We can use a very simple shiny layout to start - the sidebar layout. This is a layout where widgets are placed in a sidebar on the left, and the plot is placed on the right.\nLets plan our app - we can start with a selector that lets us choose the district where we want to visualise data, and another to let us visualise the age group we are interested in. We’ll aim to use these filters to show an epicurve that reflects these parameters. So for this we need:\n\nTwo dropdown menus that let us choose the district we want, and the age group we’re interested in.\nAn area where we can show our resulting epicurve.\n\nThis might look something like this:\n\nlibrary(shiny)\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = TRUE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\")\n )\n \n )\n)\n\nWhen app.R is run with the above UI code (with no active code in the server portion of app.R) the layout appears looking like this - note that there will be no plot if there is no server to render it, but our inputs are working!\n\n\n\n\n\n\n\n\n\nThis is a good opportunity to discuss how widgets work - note that each widget is accepting an inputId, a label, and a series of other options that are specific to the widget type. This inputId is extremely important - these are the IDs that are used to pass information from the UI to the server. For this reason, they must be unique. You should make an effort to name them something sensible, and specific to what they are interacting with in cases of larger apps.\nYou should read documentation carefully for full details on what each of these widgets do. Widgets will pass specific types of data to the server depending on the widget type, and this needs to be fully understood. For example, selectInput() will pass a character type to the server:\n\nIf we select Spring for the first widget here, it will pass the character object \"Spring\" to the server.\nIf we select two items from the dropdown menu, they will come through as a character vector (e.g. c(\"Spring\", \"Bolo\")).\n\nOther widgets will pass different types of object to the server! For example:\n\nnumericInput() will pass a numeric type object to the server\ncheckboxInput() will pass a logical type object to the server (TRUE or FALSE)\n\nIt’s also worth noting the named vector we used for the age data here. For many widgets, using a named vector as the choices will display the names of the vector as the display choices, but pass the selected value from the vector to the server. I.e. here someone can select “15+” from the drop-down menu, and the UI will pass \"malaria_rdt_15\" to the server - which happens to be the name of the column we’re interested in!\nThere are loads of widgets that you can use to do lots of things with your app. Widgets also allow you to upload files into your app, and download outputs. There are also some excellent shiny extensions that give you access to more widgets than base shiny - the shinyWidgets package is a great example of this. To look at some examples you can look at the following links:\n\nbase shiny widget gallery\nshinyWidgets gallery", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3882,7 +3882,7 @@ "href": "new_pages/shiny_basics.html#loading-data-into-our-app", "title": "43  Dashboards with Shiny", "section": "43.4 Loading data into our app", - "text": "43.4 Loading data into our app\nThe next step in our app development is getting the server up and running. To do this however, we need to get some data into our app, and figure out all the calculations we’re going to do. A shiny app is not straightforward to debug, as it’s often not clear where errors are coming from, so it’s ideal to get all our data processing and visualisation code working before we start making the server itself.\nSo given we want to make an app that shows epi curves that change based on user input, we should think about what code we would need to run this in a normal R script. We’ll need to:\n\nLoad our packages\nLoad our data\nTransform our data\nDevelop a function to visualise our data based on user inputs\n\nThis list is pretty straightforward, and shouldn’t be too hard to do. It’s now important to think about which parts of this process need to be done only once and which parts need to run in response to user inputs. This is because shiny apps generally run some code before running, which is only performed once. It will help our app’s performance if as much of our code can be moved to this section. For this example, we only need to load our data/packages and do basic transformations once, so we can put that code outside the server. This means the only thing we’ll need in the server is the code to visualise our data. Lets develop all of these componenets in a script first. However, since we’re visualising our data with a function, we can also put the code for the function outside the server so our function is in the environment when the app runs!\nFirst lets load our data. Since we’re working with a new project, and we want to make it clean, we can create a new directory called data, and add our malaria data in there. We can run this code below in a testing script we will eventually delete when we clean up the structure of our app.\n\npacman::p_load(\"tidyverse\", \"lubridate\")\n\n# read data\nmalaria_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\")) %>% \n as_tibble()\n\nprint(malaria_data)\n\n# A tibble: 3,038 × 10\n location_name data_date submitted_date Province District `malaria_rdt_0-4`\n <chr> <date> <date> <chr> <chr> <int>\n 1 Facility 1 2020-08-11 2020-08-12 North Spring 11\n 2 Facility 2 2020-08-11 2020-08-12 North Bolo 11\n 3 Facility 3 2020-08-11 2020-08-12 North Dingo 8\n 4 Facility 4 2020-08-11 2020-08-12 North Bolo 16\n 5 Facility 5 2020-08-11 2020-08-12 North Bolo 9\n 6 Facility 6 2020-08-11 2020-08-12 North Dingo 3\n 7 Facility 6 2020-08-10 2020-08-12 North Dingo 4\n 8 Facility 5 2020-08-10 2020-08-12 North Bolo 15\n 9 Facility 5 2020-08-09 2020-08-12 North Bolo 11\n10 Facility 5 2020-08-08 2020-08-12 North Bolo 19\n# ℹ 3,028 more rows\n# ℹ 4 more variables: `malaria_rdt_5-14` <int>, malaria_rdt_15 <int>,\n# malaria_tot <int>, newid <int>\n\n\nIt will be easier to work with this data if we use tidy data standards, so we should also transform into a longer data format, where age group is a column, and cases is another column. We can do this easily using what we’ve learned in the Pivoting data page.\n\nmalaria_data <- malaria_data %>%\n select(-newid) %>%\n pivot_longer(cols = starts_with(\"malaria_\"), names_to = \"age_group\", values_to = \"cases_reported\")\n\nprint(malaria_data)\n\n# A tibble: 12,152 × 7\n location_name data_date submitted_date Province District age_group \n <chr> <date> <date> <chr> <chr> <chr> \n 1 Facility 1 2020-08-11 2020-08-12 North Spring malaria_rdt_0-4 \n 2 Facility 1 2020-08-11 2020-08-12 North Spring malaria_rdt_5-14\n 3 Facility 1 2020-08-11 2020-08-12 North Spring malaria_rdt_15 \n 4 Facility 1 2020-08-11 2020-08-12 North Spring malaria_tot \n 5 Facility 2 2020-08-11 2020-08-12 North Bolo malaria_rdt_0-4 \n 6 Facility 2 2020-08-11 2020-08-12 North Bolo malaria_rdt_5-14\n 7 Facility 2 2020-08-11 2020-08-12 North Bolo malaria_rdt_15 \n 8 Facility 2 2020-08-11 2020-08-12 North Bolo malaria_tot \n 9 Facility 3 2020-08-11 2020-08-12 North Dingo malaria_rdt_0-4 \n10 Facility 3 2020-08-11 2020-08-12 North Dingo malaria_rdt_5-14\n# ℹ 12,142 more rows\n# ℹ 1 more variable: cases_reported <int>\n\n\nAnd with that we’ve finished preparing our data! This crosses items 1, 2, and 3 off our list of things to develop for our “testing R script”. The last, and most difficult task will be building a function to produce an epicurve based on user defined parameters. As mentioned previously, it’s highly recommended that anyone learning shiny first look at the section on functional programming (Writing functions) to understand how this works!\nWhen defining our function, it might be hard to think about what parameters we want to include. For functional programming with shiny, every relevent parameter will generally have a widget associated with it, so thinking about this is usually quite easy! For example in our current app, we want to be able to filter by district, and have a widget for this, so we can add a district parameter to reflect this. We don’t have any app functionality to filter by facility (for now), so we don’t need to add this as a parameter. Lets start by making a function with three parameters:\n\nThe core dataset\nThe district of choice\nThe age group of choice\n\n\nplot_epicurve <- function(data, district = \"All\", agegroup = \"malaria_tot\") {\n \n if (!(\"All\" %in% district)) {\n data <- data %>%\n filter(District %in% district)\n \n plot_title_district <- stringr::str_glue(\"{paste0(district, collapse = ', ')} districts\")\n \n } else {\n \n plot_title_district <- \"all districts\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n data <- data %>%\n filter(age_group == agegroup)\n \n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n if (agegroup == \"malaria_tot\") {\n agegroup_title <- \"All ages\"\n } else {\n agegroup_title <- stringr::str_glue(\"{str_remove(agegroup, 'malaria_rdt')} years\")\n }\n \n \n ggplot(data, aes(x = data_date, y = cases_reported)) +\n geom_col(width = 1, fill = \"darkred\") +\n theme_minimal() +\n labs(\n x = \"date\",\n y = \"number of cases\",\n title = stringr::str_glue(\"Malaria cases - {plot_title_district}\"),\n subtitle = agegroup_title\n )\n \n \n \n}\n\nWe won’t go into great detail about this function, as it’s relatively simple in how it works. One thing to note however, is we handle errors by returning NULL when it would otherwise give an error. This is because when a shiny server produces a NULL object instead of a plot object, nothing will be shown in the ui! This is important, as otherwise errors will often cause your app to stop working.\nAnother thing to note is the use of the %in% operator when evaluating the district input. As mentioned above, this could arrive as a character vector with multiple values, so using %in% is more flexible than say, ==.\nLet’s test our function!\n\nplot_epicurve(malaria_data, district = \"Bolo\", agegroup = \"malaria_rdt_0-4\")\n\n\n\n\n\n\n\n\nWith our function working, we now have to understand how this all is going to fit into our shiny app. We mentioned the concept of startup code before, but lets look at how we can actually incorporate this into the structure of our app. There are two ways we can do this!\n\nPut this code in your app.R file at the start of the script (above the UI), or\n\nCreate a new file in your app’s directory called global.R, and put the startup code in this file.\n\nIt’s worth noting at this point that it’s generally easier, especially with bigger apps, to use the second file structure, as it lets you separate your file structure in a simple way. Lets fully develop a this global.R script now. Here is what it could look like:\n\n# global.R script\n\npacman::p_load(\"tidyverse\", \"lubridate\", \"shiny\")\n\n# read data\nmalaria_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\")) %>% \n as_tibble()\n\n# clean data and pivot longer\nmalaria_data <- malaria_data %>%\n select(-newid) %>%\n pivot_longer(cols = starts_with(\"malaria_\"), names_to = \"age_group\", values_to = \"cases_reported\")\n\n\n# define plotting function\nplot_epicurve <- function(data, district = \"All\", agegroup = \"malaria_tot\") {\n \n # create plot title\n if (!(\"All\" %in% district)) { \n data <- data %>%\n filter(District %in% district)\n \n plot_title_district <- stringr::str_glue(\"{paste0(district, collapse = ', ')} districts\")\n \n } else {\n \n plot_title_district <- \"all districts\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n # filter to age group\n data <- data %>%\n filter(age_group == agegroup)\n \n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n if (agegroup == \"malaria_tot\") {\n agegroup_title <- \"All ages\"\n } else {\n agegroup_title <- stringr::str_glue(\"{str_remove(agegroup, 'malaria_rdt')} years\")\n }\n \n \n ggplot(data, aes(x = data_date, y = cases_reported)) +\n geom_col(width = 1, fill = \"darkred\") +\n theme_minimal() +\n labs(\n x = \"date\",\n y = \"number of cases\",\n title = stringr::str_glue(\"Malaria cases - {plot_title_district}\"),\n subtitle = agegroup_title\n )\n \n \n \n}\n\nEasy! One great feature of shiny is that it will understand what files named app.R, server.R, ui.R, and global.R are for, so there is no need to connect them to each other via any code. So just by having this code in global.R in the directory it will run before we start our app!.\nWe should also note that it would improve our app’s organisation if we moved the plotting function to its own file - this will be especially helpful as apps become larger. To do this, we could make another directory called funcs, and put this function in as a file called plot_epicurve.R. We could then read this function in via the following command in global.R\n\nsource(here(\"funcs\", \"plot_epicurve.R\"), local = TRUE)\n\nNote that you should always specify local = TRUE in shiny apps, since it will affect sourcing when/if the app is published on a server.", + "text": "43.4 Loading data into our app\nThe next step in our app development is getting the server up and running. To do this however, we need to get some data into our app, and figure out all the calculations we’re going to do. A shiny app is not straightforward to debug, as it’s often not clear where errors are coming from, so it’s ideal to get all our data processing and visualisation code working before we start making the server itself.\nSo given we want to make an app that shows epi curves that change based on user input, we should think about what code we would need to run this in a normal R script. We’ll need to:\n\nLoad our packages.\nLoad our data.\nTransform our data.\nDevelop a function to visualise our data based on user inputs.\n\nThis list is pretty straightforward, and shouldn’t be too hard to do. It’s now important to think about which parts of this process need to be done only once and which parts need to run in response to user inputs. This is because shiny apps generally run some code before running, which is only performed once. It will help our app’s performance if as much of our code can be moved to this section. For this example, we only need to load our data/packages and do basic transformations once, so we can put that code outside the server. This means the only thing we’ll need in the server is the code to visualise our data. Lets develop all of these componenets in a script first. However, since we’re visualising our data with a function, we can also put the code for the function outside the server so our function is in the environment when the app runs!\nFirst lets load our data. Since we’re working with a new project, and we want to make it clean, we can create a new directory called data, and add our malaria data in there. We can run this code below in a testing script we will eventually delete when we clean up the structure of our app.\n\npacman::p_load(\n \"tidyverse\", \n \"lubridate\"\n )\n\n# read data\nmalaria_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\"))\n\nhead(malaria_data)\n\n location_name data_date submitted_date Province District malaria_rdt_0-4\n1 Facility 1 2020-08-11 2020-08-12 North Spring 11\n2 Facility 2 2020-08-11 2020-08-12 North Bolo 11\n3 Facility 3 2020-08-11 2020-08-12 North Dingo 8\n4 Facility 4 2020-08-11 2020-08-12 North Bolo 16\n5 Facility 5 2020-08-11 2020-08-12 North Bolo 9\n6 Facility 6 2020-08-11 2020-08-12 North Dingo 3\n malaria_rdt_5-14 malaria_rdt_15 malaria_tot newid\n1 12 23 46 1\n2 10 5 26 2\n3 5 5 18 3\n4 16 17 49 4\n5 2 6 17 5\n6 1 4 8 6\n\n\nIt will be easier to work with this data if we use tidy data standards, so we should also transform into a longer data format, where age group is a column, and cases is another column. We can do this easily using what we’ve learned in the Pivoting data page.\n\nmalaria_data <- malaria_data %>%\n select(-newid) %>%\n pivot_longer(cols = starts_with(\"malaria_\"), \n names_to = \"age_group\", \n values_to = \"cases_reported\")\n\nhead(malaria_data)\n\n# A tibble: 6 × 7\n location_name data_date submitted_date Province District age_group \n <chr> <date> <date> <chr> <chr> <chr> \n1 Facility 1 2020-08-11 2020-08-12 North Spring malaria_rdt_0-4 \n2 Facility 1 2020-08-11 2020-08-12 North Spring malaria_rdt_5-14\n3 Facility 1 2020-08-11 2020-08-12 North Spring malaria_rdt_15 \n4 Facility 1 2020-08-11 2020-08-12 North Spring malaria_tot \n5 Facility 2 2020-08-11 2020-08-12 North Bolo malaria_rdt_0-4 \n6 Facility 2 2020-08-11 2020-08-12 North Bolo malaria_rdt_5-14\n# ℹ 1 more variable: cases_reported <int>\n\n\nAnd with that we’ve finished preparing our data! This crosses items 1, 2, and 3 off our list of things to develop for our “testing R script”. The last, and most difficult task will be building a function to produce an epicurve based on user defined parameters. As mentioned previously, it’s highly recommended that anyone learning shiny first look at the section on functional programming (Writing functions) to understand how this works!\nWhen defining our function, it might be hard to think about what parameters we want to include. For functional programming with shiny, every relevent parameter will generally have a widget associated with it, so thinking about this is usually quite easy! For example in our current app, we want to be able to filter by district, and have a widget for this, so we can add a district parameter to reflect this. We don’t have any app functionality to filter by facility (for now), so we don’t need to add this as a parameter. Lets start by making a function with three parameters:\n\nThe core dataset.\nThe district of choice.\nThe age group of choice.\n\n\nplot_epicurve <- function(data, district = \"All\", agegroup = \"malaria_tot\") {\n \n if (!(\"All\" %in% district)) {\n data <- data %>%\n filter(District %in% district)\n \n plot_title_district <- stringr::str_glue(\"{paste0(district, collapse = ', ')} districts\")\n \n } else {\n \n plot_title_district <- \"all districts\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n data <- data %>%\n filter(age_group == agegroup)\n \n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n if (agegroup == \"malaria_tot\") {\n agegroup_title <- \"All ages\"\n } else {\n agegroup_title <- stringr::str_glue(\"{str_remove(agegroup, 'malaria_rdt')} years\")\n }\n \n \n ggplot(data, \n mapping = aes(x = data_date, \n y = cases_reported)\n ) +\n geom_col(width = 1, \n fill = \"darkred\"\n ) +\n theme_minimal() +\n labs(\n x = \"date\",\n y = \"number of cases\",\n title = stringr::str_glue(\"Malaria cases - {plot_title_district}\"),\n subtitle = agegroup_title\n )\n \n \n \n}\n\nWe won’t go into great detail about this function, as it’s relatively simple in how it works. One thing to note however, is we handle errors by returning NULL when it would otherwise give an error. This is because when a shiny server produces a NULL object instead of a plot object, nothing will be shown in the ui! This is important, as otherwise errors will often cause your app to stop working.\nAnother thing to note is the use of the %in% operator when evaluating the district input. As mentioned above, this could arrive as a character vector with multiple values, so using %in% is more flexible than say, ==.\nLet’s test our function!\n\nplot_epicurve(malaria_data, \n district = \"Bolo\", \n agegroup = \"malaria_rdt_0-4\")\n\n\n\n\n\n\n\n\nWith our function working, we now have to understand how this all is going to fit into our shiny app. We mentioned the concept of startup code before, but lets look at how we can actually incorporate this into the structure of our app. There are two ways we can do this!\n\nPut this code in your app.R file at the start of the script (above the UI), or,\n\nCreate a new file in your app’s directory called global.R, and put the startup code in this file.\n\nIt’s worth noting at this point that it’s generally easier, especially with bigger apps, to use the second file structure, as it lets you separate your file structure in a simple way. Lets fully develop a this global.R script now. Here is what it could look like:\n\n# global.R script\n\npacman::p_load(\"tidyverse\", \"lubridate\", \"shiny\")\n\n# read data\nmalaria_data <- rio::import(here::here(\"data\", \"malaria_facility_count_data.rds\")) %>% \n as_tibble()\n\n# clean data and pivot longer\nmalaria_data <- malaria_data %>%\n select(-newid) %>%\n pivot_longer(cols = starts_with(\"malaria_\"), \n names_to = \"age_group\", \n values_to = \"cases_reported\")\n\n\n# define plotting function\nplot_epicurve <- function(data, district = \"All\", agegroup = \"malaria_tot\") {\n \n # create plot title\n if (!(\"All\" %in% district)) { \n data <- data %>%\n filter(District %in% district)\n \n plot_title_district <- stringr::str_glue(\"{paste0(district, collapse = ', ')} districts\")\n \n } else {\n \n plot_title_district <- \"all districts\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n # filter to age group\n data <- data %>%\n filter(age_group == agegroup)\n \n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n if (agegroup == \"malaria_tot\") {\n agegroup_title <- \"All ages\"\n } else {\n agegroup_title <- stringr::str_glue(\"{str_remove(agegroup, 'malaria_rdt')} years\")\n }\n \n ggplot(data, \n mapping = aes(x = data_date, \n y = cases_reported)\n ) +\n geom_col(width = 1, \n fill = \"darkred\") +\n theme_minimal() +\n labs(\n x = \"date\",\n y = \"number of cases\",\n title = stringr::str_glue(\"Malaria cases - {plot_title_district}\"),\n subtitle = agegroup_title\n )\n\n}\n\nEasy! One great feature of shiny is that it will understand what files named app.R, server.R, ui.R, and global.R are for, so there is no need to connect them to each other via any code. So just by having this code in global.R in the directory it will run before we start our app!.\nWe should also note that it would improve our app’s organisation if we moved the plotting function to its own file - this will be especially helpful as apps become larger. To do this, we could make another directory called funcs, and put this function in as a file called plot_epicurve.R. We could then read this function in via the following command in global.R\n\nsource(here(\"funcs\", \"plot_epicurve.R\"), local = TRUE)\n\nNote that you should always specify local = TRUE in shiny apps, since it will affect sourcing when/if the app is published on a server.", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3893,7 +3893,7 @@ "href": "new_pages/shiny_basics.html#developing-an-app-server", "title": "43  Dashboards with Shiny", "section": "43.5 Developing an app server", - "text": "43.5 Developing an app server\nNow that we have most of our code, we just have to develop our server. This is the final piece of our app, and is probably the hardest to understand. The server is a large R function, but its helpful to think of it as a series of smaller functions, or tasks that the app can perform. It’s important to understand that these functions are not executed in a linear order. There is an order to them, but it’s not fully necessary to understand when starting out with shiny. At a very basic level, these tasks or functions will activate when there is a change in user inputs that affects them, unless the developer has set them up so they behave differently. Again, this is all quite abstract, but lets first go through the three basic types of shiny objects\n\nReactive sources - this is another term for user inputs. The shiny server has access to the outputs from the UI through the widgets we’ve programmed. Every time the values for these are changed, this is passed down to the server.\nReactive conductors - these are objects that exist only inside the shiny server. We don’t actually need these for simple apps, but they produce objects that can only be seen inside the server, and used in other operations. They generally depend on reactive sources.\nEndpoints - these are outputs that are passed from the server to the UI. In our example, this would be the epi curve we are producing.\n\nWith this in mind lets construct our server step-by-step. We’ll show our UI code again here just for reference:\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = TRUE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\")\n )\n \n )\n)\n\nFrom this code UI we have:\n\nTwo inputs:\n\nDistrict selector (with an inputId of select_district)\nAge group selector (with an inputId of select_agegroup)\n\nOne output:\n\nThe epicurve (with an outputId of malaria_epicurve)\n\n\nAs stated previously, these unique names we have assigned to our inputs and outputs are crucial. They must be unique and are used to pass information between the ui and server. In our server, we access our inputs via the syntax input$inputID and outputs and passed to the ui through the syntax output$output_name Lets have a look at an example, because again this is hard to understand otherwise!\n\nserver <- function(input, output, session) {\n \n output$malaria_epicurve <- renderPlot(\n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)\n )\n \n}\n\nThe server for a simple app like this is actually quite straightforward! You’ll notice that the server is a function with three parameters - input, output, and session - this isn’t that important to understand for now, but its important to stick to this setup! In our server we only have one task - this renders a plot based on our function we made earlier, and the inputs from the server. Notice how the names of the input and output objects correspond exactly to those in the ui.\nTo understand the basics of how the server reacts to user inputs, you should note that the output will know (through the underlying package) when inputs change, and rerun this function to create a plot every time they change. Note that we also use the renderPlot() function here - this is one of a family of class-specific functions that pass those objects to a ui output. There are a number of functions that behave similarly, but you need to ensure the function used matches the class of object you’re passing to the ui! For example:\n\nrenderText() - send text to the ui\nrenderDataTable - send an interactive table to the ui.\n\nRemember that these also need to match the output function used in the ui - so renderPlot() is paired with plotOutput(), and renderText() is matched with textOutput().\nSo we’ve finally made a functioning app! We can run this by pressing the Run App button on the top right of the script window in Rstudio. You should note that you can choose to run your app in your default browser (rather than Rstudio) which will more accurately reflect what the app will look like for other users.\n\n\n\n\n\n\n\n\n\nIt is fun to note that in the R console, the app is “listening”! Talk about reactivity!", + "text": "43.5 Developing an app server\nNow that we have most of our code, we just have to develop our server. This is the final piece of our app, and is probably the hardest to understand. The server is a large R function, but its helpful to think of it as a series of smaller functions, or tasks that the app can perform. It’s important to understand that these functions are not executed in a linear order. There is an order to them, but it’s not fully necessary to understand when starting out with shiny. At a very basic level, these tasks or functions will activate when there is a change in user inputs that affects them, unless the developer has set them up so they behave differently. Again, this is all quite abstract, but lets first go through the three basic types of shiny objects:\n\nReactive sources - this is another term for user inputs. The shiny server has access to the outputs from the UI through the widgets we’ve programmed. Every time the values for these are changed, this is passed down to the server.\nReactive conductors - these are objects that exist only inside the shiny server. We don’t actually need these for simple apps, but they produce objects that can only be seen inside the server, and used in other operations. They generally depend on reactive sources.\nEndpoints - these are outputs that are passed from the server to the UI. In our example, this would be the epi curve we are producing.\n\nWith this in mind lets construct our server step-by-step. We’ll show our UI code again here just for reference:\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = TRUE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\")\n )\n \n )\n)\n\nFrom this code UI we have:\n\nTwo inputs:\n\nDistrict selector (with an inputId of select_district).\nAge group selector (with an inputId of select_agegroup).\n\nOne output:\n\nThe epicurve (with an outputId of malaria_epicurve).\n\n\nAs stated previously, these unique names we have assigned to our inputs and outputs are crucial. They must be unique and are used to pass information between the ui and server. In our server, we access our inputs via the syntax input$inputID and outputs and passed to the ui through the syntax output$output_name Lets have a look at an example, because again this is hard to understand otherwise!\n\nserver <- function(input, output, session) {\n \n output$malaria_epicurve <- renderPlot(\n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup)\n )\n \n}\n\nThe server for a simple app like this is actually quite straightforward! You’ll notice that the server is a function with three parameters - input, output, and session - this isn’t that important to understand for now, but its important to stick to this setup! In our server we only have one task - this renders a plot based on our function we made earlier, and the inputs from the server. Notice how the names of the input and output objects correspond exactly to those in the ui.\nTo understand the basics of how the server reacts to user inputs, you should note that the output will know (through the underlying package) when inputs change, and rerun this function to create a plot every time they change. Note that we also use the renderPlot() function here - this is one of a family of class-specific functions that pass those objects to a ui output. There are a number of functions that behave similarly, but you need to ensure the function used matches the class of object you’re passing to the ui! For example:\n\nrenderText() - send text to the ui.\nrenderDataTable - send an interactive table to the ui.\n\nRemember that these also need to match the output function used in the ui - so renderPlot() is paired with plotOutput(), and renderText() is matched with textOutput().\nSo we’ve finally made a functioning app! We can run this by pressing the Run App button on the top right of the script window in Rstudio. You should note that you can choose to run your app in your default browser (rather than Rstudio) which will more accurately reflect what the app will look like for other users.\n\n\n\n\n\n\n\n\n\nIt is fun to note that in the R console, the app is “listening”! Talk about reactivity!", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3904,7 +3904,7 @@ "href": "new_pages/shiny_basics.html#adding-more-functionality", "title": "43  Dashboards with Shiny", "section": "43.6 Adding more functionality", - "text": "43.6 Adding more functionality\nAt this point we’ve finally got a running app, but we have very little functionality. We also haven’t really scratched the surface of what shiny can do, so there’s a lot more to learn about! Lets continue to build our existing app by adding some extra features. Some things that could be nice to add could be:\n\nSome explanatory text\nA download button for our plot - this would provide the user with a high quality version of the image that they’re generating in the app\nA selector for specific facilities\nAnother dashboard page - this could show a table of our data.\n\nThis is a lot to add, but we can use it to learn about a bunch of different shiny featues on the way. There is so much to learn about shiny (it can get very advanced, but its hopefully the case that once users have a better idea of how to use it they can become more comfortable using external learning sources as well).\n\nAdding static text\nLets first discuss adding static text to our shiny app. Adding text to our app is extremely easy, once you have a basic grasp of it. Since static text doesn’t change in the shiny app (If you’d like it to change, you can use text rendering functions in the server!), all of shiny’s static text is generally added in the ui of the app. We wont go through this in great detail, but you can add a number of different elements to your ui (and even custom ones) by interfacing R with HTML and css.\nHTML and css are languages that are explicitly involved in user interface design. We don’t need to understand these too well, but HTML creates objects in UI (like a text box, or a table), and css is generally used to change the style and aesthetics of those objects. Shiny has access to a large array of HTML tags - these are present for objects that behave in a specific way, such as headers, paragraphs of text, line breaks, tables, etc. We can use some of these examples like this:\n\nh1() - this a a header tag, which will make enclosed text automatically larger, and change defaults as they pertain to the font face, colour etc (depending on the overall theme of your app). You can access smaller and smaller sub-heading with h2() down to h6() as well. Usage looks like:\n\nh1(\"my header - section 1\")\n\np() - this is a paragraph tag, which will make enclosed text similar to text in a body of text. This text will automatically wrap, and be of a relatively small size (footers could be smaller for example.) Think of it as the text body of a word document. Usage looks like:\n\np(\"This is a larger body of text where I am explaining the function of my app\")\n\ntags$b() and tags$i() - these are used to create bold tags$b() and italicised tags$i() with whichever text is enclosed!\ntags$ul(), tags$ol() and tags$li() - these are tags used in creating lists. These are all used within the syntax below, and allow the user to create either an ordered list (tags$ol(); i.e. numbered) or unordered list (tags$ul(), i.e. bullet points). tags$li() is used to denote items in the list, regardless of which type of list is used. e.g.:\n\n\ntags$ol(\n \n tags$li(\"Item 1\"),\n \n tags$li(\"Item 2\"),\n \n tags$li(\"Item 3\")\n \n)\n\n\nbr() and hr() - these tags create linebreaks and horizontal lines (with a linebreak) respectively. Use them to separate out the sections of your app and text! There is no need to pass any items to these tags (parentheses can remain empty).\ndiv() - this is a generic tag that can contain anything, and can be named anything. Once you progress with ui design, you can use these to compartmentalize your ui, give specific sections specific styles, and create interactions between the server and UI elements. We won’t go into these in detail, but they’re worth being aware of!\n\nNote that every one of these objects can be accessed through tags$... or for some, just the function. These are effectively synonymous, but it may help to use the tags$... style if you’d rather be more explicit and not overwrite the functions accidentally. This is also by no means an exhaustive list of tags available. There is a full list of all tags available in shiny here and even more can be used by inserting HTML directly into your ui!\nIf you’re feeling confident, you can also add any css styling elements to your HTML tags with the style argument in any of them. We won’t go into how this works in detail, but one tip for testing aesthetic changes to a UI is using the HTML inspector mode in chrome (of your shiny app you are running in browser), and editing the style of objects yourself!\nLets add some text to our app\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n h4(\"Options\"),\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = TRUE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\"),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n )\n)\n)\n\n\n\n\n\n\n\n\n\n\n\n\nAdding a link\nTo add a link to a website, use tags$a() with the link and display text as shown below. To have as a standalone paragraph, put it within p(). To have only a few words of a sentence linked, break the sentence into parts and use tags$a() for the hyperlinked part. To ensure the link opens in a new browser window, add target = \"_blank\" as an argument.\n\ntags$a(href = \"www.epiRhandbook.com\", \"Visit our website!\")\n\n\n\nAdding a download button\nLets move on to the second of the three features. A download button is a fairly common thing to add to an app and is fairly easy to make. We need to add another Widget to our ui, and we need to add another output to our server to attach to it. We can also introduce reactive conductors in this example!\nLets update our ui first - this is easy as shiny comes with a widget called downloadButton() - lets give it an inputId and a label.\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = FALSE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n # horizontal line\n hr(),\n downloadButton(\n outputId = \"download_epicurve\",\n label = \"Download plot\"\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\"),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n )\n \n )\n)\n\nNote that we’ve also added in a hr() tag - this adds a horizontal line separating our control widgets from our download widgets. This is another one of the HTML tags that we discussed previously.\nNow that we have our ui ready, we need to add the server component. Downloads are done in the server with the downloadHandler() function. Similar to our plot, we need to attach it to an output that has the same inputId as the download button. This function takes two arguments - filename and content - these are both functions. As you might be able to guess, filename is used to specify the name of the downloaded file, and content is used to specify what should be downloaded. content contain a function that you would use to save data locally - so if you were downloading a csv file you could use rio::export(). Since we’re downloading a plot, we’ll use ggplot2::ggsave(). Lets look at how we would program this (we won’t add it to the server yet).\n\nserver <- function(input, output, session) {\n \n output$malaria_epicurve <- renderPlot(\n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)\n )\n \n output$download_epicurve <- downloadHandler(\n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n}\n\nNote that the content function always takes a file argument, which we put where the output file name is specified. You might also notice that we’re repeating code here - we are using our plot_epicurve() function twice in this server, once for the download and once for the image displayed in the app. While this wont massively affect performance, this means that the code to generate this plot will have to be run when the user changes the widgets specifying the district and age group, and again when you want to download the plot. In larger apps, suboptimal decisions like this one will slow things down more and more, so it’s good to learn how to make our app more efficient in this sense. What would make more sense is if we had a way to run the epicurve code when the districts/age groups are changes, and let that be used by the renderPlot() and downloadHandler() functions. This is where reactive conductors come in!\nReactive conductors are objects that are created in the shiny server in a reactive way, but are not outputted - they can just be used by other parts of the server. There are a number of different kinds of reactive conductors, but we’ll go through the basic two.\n1.reactive() - this is the most basic reactive conductor - it will react whenever any inputs used inside of it change (so our district/age group widgets)\n2. eventReactive()- this rective conductor works the same as reactive(), except that the user can specify which inputs cause it to rerun. This is useful if your reactive conductor takes a long time to process, but this will be explained more later.\nLets look at the two examples:\n\nmalaria_plot_r <- reactive({\n \n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)\n \n})\n\n\n# only runs when the district selector changes!\nmalaria_plot_er <- eventReactive(input$select_district, {\n \n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)\n \n})\n\nWhen we use the eventReactive() setup, we can specify which inputs cause this chunk of code to run - this isn’t very useful to us at the moment, so we can leave it for now. Note that you can include multiple inputs with c()\nLets look at how we can integrate this into our server code:\n\nserver <- function(input, output, session) {\n \n malaria_plot <- reactive({\n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup)\n })\n \n \n \n output$malaria_epicurve <- renderPlot(\n malaria_plot()\n )\n \n output$download_epicurve <- downloadHandler(\n \n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n malaria_plot(),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n}\n\nYou can see we’re just calling on the output of our reactive we’ve defined in both our download and plot rendering functions. One thing to note that often trips people up is you have to use the outputs of reactives as if they were functions - so you must add empty brackets at the end of them (i.e. malaria_plot() is correct, and malaria_plot is not). Now that we’ve added this solution our app is a little tidyer, faster, and easier to change since all our code that runs the epicurve function is in one place.\n\n\n\n\n\n\n\n\n\n\n\nAdding a facility selector\nLets move on to our next feature - a selector for specific facilities. We’ll implement another parameter into our function so we can pass this as an argument from our code. Lets look at doing this first - it just operates off the same principles as the other parameters we’ve set up. Lets update and test our function.\n\nplot_epicurve <- function(data, district = \"All\", agegroup = \"malaria_tot\", facility = \"All\") {\n \n if (!(\"All\" %in% district)) {\n data <- data %>%\n filter(District %in% district)\n \n plot_title_district <- stringr::str_glue(\"{paste0(district, collapse = ', ')} districts\")\n \n } else {\n \n plot_title_district <- \"all districts\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n data <- data %>%\n filter(age_group == agegroup)\n \n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n if (agegroup == \"malaria_tot\") {\n agegroup_title <- \"All ages\"\n } else {\n agegroup_title <- stringr::str_glue(\"{str_remove(agegroup, 'malaria_rdt')} years\")\n }\n \n if (!(\"All\" %in% facility)) {\n data <- data %>%\n filter(location_name == facility)\n \n plot_title_facility <- facility\n \n } else {\n \n plot_title_facility <- \"all facilities\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n\n \n \n ggplot(data, aes(x = data_date, y = cases_reported)) +\n geom_col(width = 1, fill = \"darkred\") +\n theme_minimal() +\n labs(\n x = \"date\",\n y = \"number of cases\",\n title = stringr::str_glue(\"Malaria cases - {plot_title_district}; {plot_title_facility}\"),\n subtitle = agegroup_title\n )\n \n \n \n}\n\nLet’s test it:\n\nplot_epicurve(malaria_data, district = \"Spring\", agegroup = \"malaria_rdt_0-4\", facility = \"Facility 1\")\n\n\n\n\n\n\n\n\nWith all the facilites in our data, it isn’t very clear which facilities correspond to which districts - and the end user won’t know either. This might make using the app quite unintuitive. For this reason, we should make the facility options in the UI change dynamically as the user changes the district - so one filters the other! Since we have so many variables that we’re using in the options, we might also want to generate some of our options for the ui in our global.R file from the data. For example, we can add this code chunk to global.R after we’ve read our data in:\n\nall_districts <- c(\"All\", unique(malaria_data$District))\n\n# data frame of location names by district\nfacility_list <- malaria_data %>%\n group_by(location_name, District) %>%\n summarise() %>% \n ungroup()\n\nLet’s look at them:\n\nall_districts\n\n[1] \"All\" \"Spring\" \"Bolo\" \"Dingo\" \"Barnard\"\n\n\n\nfacility_list\n\n# A tibble: 65 × 2\n location_name District\n <chr> <chr> \n 1 Facility 1 Spring \n 2 Facility 10 Bolo \n 3 Facility 11 Spring \n 4 Facility 12 Dingo \n 5 Facility 13 Bolo \n 6 Facility 14 Dingo \n 7 Facility 15 Barnard \n 8 Facility 16 Barnard \n 9 Facility 17 Barnard \n10 Facility 18 Bolo \n# ℹ 55 more rows\n\n\nWe can pass these new variables to the ui without any issue, since they are globally visible by both the server and the ui! Lets update our UI:\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = all_districts,\n selected = \"All\",\n multiple = FALSE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n # selector for facility\n selectInput(\n inputId = \"select_facility\",\n label = \"Select Facility\",\n choices = c(\"All\", facility_list$location_name),\n selected = \"All\"\n ),\n \n # horizontal line\n hr(),\n downloadButton(\n outputId = \"download_epicurve\",\n label = \"Download plot\"\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\"),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n )\n \n )\n)\n\nNotice how we’re now passing variables for our choices instead of hard coding them in the ui! This might make our code more compact as well! Lastly, we’ll have to update the server. It will be easy to update our function to incorporate our new input (we just have to pass it as an argument to our new parameter), but we should remember we also want the ui to update dynamically when the user changes the selected district. It is important to understand here that we can change the parameters and behaviour of widgets while the app is running, but this needs to be done in the server. We need to understand a new way to output to the server to learn how to do this.\nThe functions we need to understand how to do this are known as observer functions, and are similar to reactive functions in how they behave. They have one key difference though:\n\nReactive functions do not directly affect outputs, and produce objects that can be seen in other locations in the server\nObserver functions can affect server outputs, but do so via side effects of other functions. (They can also do other things, but this is their main function in practice)\n\nSimilar to reactive functions, there are two flavours of observer functions, and they are divided by the same logic that divides reactive functions:\n\nobserve() - this function runs whenever any inputs used inside of it change\nobserveEvent() - this function runs when a user-specified input changes\n\nWe also need to understand the shiny-provided functions that update widgets. These are fairly straightforward to run - they first take the session object from the server function (this doesn’t need to be understood for now), and then the inputId of the function to be changed. We then pass new versions of all parameters that are already taken by selectInput() - these will be automatically updated in the widget.\nLets look at an isolated example of how we could use this in our server. When the user changes the district, we want to filter our tibble of facilities by district, and update the choices to only reflect those that are available in that district (and an option for all facilities)\n\nobserve({\n \n if (input$select_district == \"All\") {\n new_choices <- facility_list$location_name\n } else {\n new_choices <- facility_list %>%\n filter(District == input$select_district) %>%\n pull(location_name)\n }\n \n new_choices <- c(\"All\", new_choices)\n \n updateSelectInput(session, inputId = \"select_facility\",\n choices = new_choices)\n \n})\n\nAnd that’s it! we can add it into our server, and that behaviour will now work. Here’s what our new server should look like:\n\nserver <- function(input, output, session) {\n \n malaria_plot <- reactive({\n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility)\n })\n \n \n \n observe({\n \n if (input$select_district == \"All\") {\n new_choices <- facility_list$location_name\n } else {\n new_choices <- facility_list %>%\n filter(District == input$select_district) %>%\n pull(location_name)\n }\n \n new_choices <- c(\"All\", new_choices)\n \n updateSelectInput(session, inputId = \"select_facility\",\n choices = new_choices)\n \n })\n \n \n output$malaria_epicurve <- renderPlot(\n malaria_plot()\n )\n \n output$download_epicurve <- downloadHandler(\n \n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n malaria_plot(),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n \n \n}\n\n\n\n\n\n\n\n\n\n\n\n\nAdding another tab with a table\nNow we’ll move on to the last component we want to add to our app. We’ll want to separate our ui into two tabs, one of which will have an interactive table where the user can see the data they are making the epidemic curve with. To do this, we can use the packaged ui elements that come with shiny relevant to tabs. On a basic level, we can enclose most of our main panel in this general structure:\n\n# ... the rest of ui\n\nmainPanel(\n \n tabsetPanel(\n type = \"tabs\",\n tabPanel(\n \"Epidemic Curves\",\n ...\n ),\n tabPanel(\n \"Data\",\n ...\n )\n )\n)\n\nLets apply this to our ui. We also will want to use the DT package here - this is a great package for making interactive tables from pre-existing data. We can see it being used for DT::datatableOutput() in this example.\n\nui <- fluidPage(\n \n titlePanel(\"Malaria facility visualisation app\"),\n \n sidebarLayout(\n \n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = all_districts,\n selected = \"All\",\n multiple = FALSE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n # selector for facility\n selectInput(\n inputId = \"select_facility\",\n label = \"Select Facility\",\n choices = c(\"All\", facility_list$location_name),\n selected = \"All\"\n ),\n \n # horizontal line\n hr(),\n downloadButton(\n outputId = \"download_epicurve\",\n label = \"Download plot\"\n )\n \n ),\n \n mainPanel(\n tabsetPanel(\n type = \"tabs\",\n tabPanel(\n \"Epidemic Curves\",\n plotOutput(\"malaria_epicurve\")\n ),\n tabPanel(\n \"Data\",\n DT::dataTableOutput(\"raw_data\")\n )\n ),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n \n )\n )\n)\n\nNow our app is arranged into tabs! Lets make the necessary edits to the server as well. Since we dont need to manipulate our dataset at all before we render it this is actually very simple - we just render the malaria_data dataset via DT::renderDT() to the ui!\n\nserver <- function(input, output, session) {\n \n malaria_plot <- reactive({\n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility)\n })\n \n \n \n observe({\n \n if (input$select_district == \"All\") {\n new_choices <- facility_list$location_name\n } else {\n new_choices <- facility_list %>%\n filter(District == input$select_district) %>%\n pull(location_name)\n }\n \n new_choices <- c(\"All\", new_choices)\n \n updateSelectInput(session, inputId = \"select_facility\",\n choices = new_choices)\n \n })\n \n \n output$malaria_epicurve <- renderPlot(\n malaria_plot()\n )\n \n output$download_epicurve <- downloadHandler(\n \n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n malaria_plot(),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n # render data table to ui\n output$raw_data <- DT::renderDT(\n malaria_data\n )\n \n \n}", + "text": "43.6 Adding more functionality\nAt this point we’ve finally got a running app, but we have very little functionality. We also haven’t really scratched the surface of what shiny can do, so there’s a lot more to learn about! Lets continue to build our existing app by adding some extra features. Some things that could be nice to add could be:\n\nSome explanatory text.\nA download button for our plot - this would provide the user with a high quality version of the image that they’re generating in the app.\nA selector for specific facilities.\nAnother dashboard page - this could show a table of our data.\n\nThis is a lot to add, but we can use it to learn about a bunch of different shiny featues on the way. There is so much to learn about shiny (it can get very advanced, but its hopefully the case that once users have a better idea of how to use it they can become more comfortable using external learning sources as well).\n\nAdding static text\nLets first discuss adding static text to our shiny app. Adding text to our app is extremely easy, once you have a basic grasp of it. Since static text doesn’t change in the shiny app (If you’d like it to change, you can use text rendering functions in the server!), all of shiny’s static text is generally added in the ui of the app. We wont go through this in great detail, but you can add a number of different elements to your ui (and even custom ones) by interfacing R with HTML and css.\nHTML and css are languages that are explicitly involved in user interface design. We don’t need to understand these too well, but HTML creates objects in UI (like a text box, or a table), and css is generally used to change the style and aesthetics of those objects. Shiny has access to a large array of HTML tags - these are present for objects that behave in a specific way, such as headers, paragraphs of text, line breaks, tables, etc. We can use some of these examples like this:\n\nh1() - this a a header tag, which will make enclosed text automatically larger, and change defaults as they pertain to the font face, colour etc (depending on the overall theme of your app). You can access smaller and smaller sub-heading with h2() down to h6() as well. Usage looks like:\n\nh1(\"my header - section 1\")\n\np() - this is a paragraph tag, which will make enclosed text similar to text in a body of text. This text will automatically wrap, and be of a relatively small size (footers could be smaller for example.) Think of it as the text body of a word document. Usage looks like:\n\np(\"This is a larger body of text where I am explaining the function of my app\")\n\ntags$b() and tags$i() - these are used to create bold tags$b() and italicised tags$i() with whichever text is enclosed!\ntags$ul(), tags$ol() and tags$li() - these are tags used in creating lists. These are all used within the syntax below, and allow the user to create either an ordered list (tags$ol(); i.e. numbered) or unordered list (tags$ul(), i.e. bullet points). tags$li() is used to denote items in the list, regardless of which type of list is used. e.g.:\n\n\ntags$ol(\n \n tags$li(\"Item 1\"),\n \n tags$li(\"Item 2\"),\n \n tags$li(\"Item 3\")\n \n)\n\n\nbr() and hr() - these tags create linebreaks and horizontal lines (with a linebreak) respectively. Use them to separate out the sections of your app and text! There is no need to pass any items to these tags (parentheses can remain empty).\ndiv() - this is a generic tag that can contain anything, and can be named anything. Once you progress with ui design, you can use these to compartmentalize your ui, give specific sections specific styles, and create interactions between the server and UI elements. We won’t go into these in detail, but they’re worth being aware of!\n\nNote that every one of these objects can be accessed through tags$... or for some, just the function. These are effectively synonymous, but it may help to use the tags$... style if you’d rather be more explicit and not overwrite the functions accidentally. This is also by no means an exhaustive list of tags available. There is a full list of all tags available in shiny here and even more can be used by inserting HTML directly into your ui!\nIf you’re feeling confident, you can also add any css styling elements to your HTML tags with the style argument in any of them. We won’t go into how this works in detail, but one tip for testing aesthetic changes to a UI is using the HTML inspector mode in chrome (of your shiny app you are running in browser), and editing the style of objects yourself!\nLets add some text to our app\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n h4(\"Options\"),\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = TRUE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\"),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n )\n)\n)\n\n\n\n\n\n\n\n\n\n\n\n\nAdding a link\nTo add a link to a website, use tags$a() with the link and display text as shown below. To have as a standalone paragraph, put it within p(). To have only a few words of a sentence linked, break the sentence into parts and use tags$a() for the hyperlinked part. To ensure the link opens in a new browser window, add target = \"_blank\" as an argument.\n\ntags$a(href = \"www.epiRhandbook.com\", \"Visit our website!\")\n\n\n\nAdding a download button\nLets move on to the second of the three features. A download button is a fairly common thing to add to an app and is fairly easy to make. We need to add another Widget to our ui, and we need to add another output to our server to attach to it. We can also introduce reactive conductors in this example!\nLets update our ui first - this is easy as shiny comes with a widget called downloadButton() - lets give it an inputId and a label.\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = c(\n \"All\",\n \"Spring\",\n \"Bolo\",\n \"Dingo\",\n \"Barnard\"\n ),\n selected = \"All\",\n multiple = FALSE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n # horizontal line\n hr(),\n downloadButton(\n outputId = \"download_epicurve\",\n label = \"Download plot\"\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\"),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n )\n \n )\n)\n\nNote that we’ve also added in a hr() tag - this adds a horizontal line separating our control widgets from our download widgets. This is another one of the HTML tags that we discussed previously.\nNow that we have our ui ready, we need to add the server component. Downloads are done in the server with the downloadHandler() function. Similar to our plot, we need to attach it to an output that has the same inputId as the download button. This function takes two arguments - filename and content - these are both functions. As you might be able to guess, filename is used to specify the name of the downloaded file, and content is used to specify what should be downloaded. content contain a function that you would use to save data locally - so if you were downloading a csv file you could use rio::export(). Since we’re downloading a plot, we’ll use ggplot2::ggsave(). Lets look at how we would program this (we won’t add it to the server yet).\n\nserver <- function(input, output, session) {\n \n output$malaria_epicurve <- renderPlot(\n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup)\n )\n \n output$download_epicurve <- downloadHandler(\n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n}\n\nNote that the content function always takes a file argument, which we put where the output file name is specified. You might also notice that we’re repeating code here - we are using our plot_epicurve() function twice in this server, once for the download and once for the image displayed in the app. While this wont massively affect performance, this means that the code to generate this plot will have to be run when the user changes the widgets specifying the district and age group, and again when you want to download the plot. In larger apps, suboptimal decisions like this one will slow things down more and more, so it’s good to learn how to make our app more efficient in this sense. What would make more sense is if we had a way to run the epicurve code when the districts/age groups are changes, and let that be used by the renderPlot() and downloadHandler() functions. This is where reactive conductors come in!\nReactive conductors are objects that are created in the shiny server in a reactive way, but are not outputted - they can just be used by other parts of the server. There are a number of different kinds of reactive conductors, but we’ll go through the basic two.\n1.reactive() - this is the most basic reactive conductor - it will react whenever any inputs used inside of it change (so our district/age group widgets).\n2. eventReactive()- this rective conductor works the same as reactive(), except that the user can specify which inputs cause it to rerun. This is useful if your reactive conductor takes a long time to process, but this will be explained more later.\nLets look at the two examples:\n\nmalaria_plot_r <- reactive({\n \n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup)\n \n})\n\n\n# only runs when the district selector changes!\nmalaria_plot_er <- eventReactive(input$select_district, {\n \n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup)\n \n})\n\nWhen we use the eventReactive() setup, we can specify which inputs cause this chunk of code to run - this isn’t very useful to us at the moment, so we can leave it for now. Note that you can include multiple inputs with c()\nLets look at how we can integrate this into our server code:\n\nserver <- function(input, output, session) {\n \n malaria_plot <- reactive({\n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup)\n })\n \n \n \n output$malaria_epicurve <- renderPlot(\n malaria_plot()\n )\n \n output$download_epicurve <- downloadHandler(\n \n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n malaria_plot(),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n}\n\nYou can see we’re just calling on the output of our reactive we’ve defined in both our download and plot rendering functions. One thing to note that often trips people up is you have to use the outputs of reactives as if they were functions - so you must add empty brackets at the end of them (i.e. malaria_plot() is correct, and malaria_plot is not). Now that we’ve added this solution our app is a little tidyer, faster, and easier to change since all our code that runs the epicurve function is in one place.\n\n\n\n\n\n\n\n\n\n\n\nAdding a facility selector\nLets move on to our next feature - a selector for specific facilities. We’ll implement another parameter into our function so we can pass this as an argument from our code. Lets look at doing this first - it just operates off the same principles as the other parameters we’ve set up. Lets update and test our function.\n\nplot_epicurve <- function(data, district = \"All\", agegroup = \"malaria_tot\", facility = \"All\") {\n \n if (!(\"All\" %in% district)) {\n data <- data %>%\n filter(District %in% district)\n \n plot_title_district <- stringr::str_glue(\"{paste0(district, collapse = ', ')} districts\")\n \n } else {\n \n plot_title_district <- \"all districts\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n data <- data %>%\n filter(age_group == agegroup)\n \n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n \n if (agegroup == \"malaria_tot\") {\n agegroup_title <- \"All ages\"\n } else {\n agegroup_title <- stringr::str_glue(\"{str_remove(agegroup, 'malaria_rdt')} years\")\n }\n \n if (!(\"All\" %in% facility)) {\n data <- data %>%\n filter(location_name == facility)\n \n plot_title_facility <- facility\n \n } else {\n \n plot_title_facility <- \"all facilities\"\n \n }\n \n # if no remaining data, return NULL\n if (nrow(data) == 0) {\n \n return(NULL)\n }\n\n \n \n ggplot(data, \n mapping = aes(x = data_date, \n y = cases_reported)\n ) +\n geom_col(width = 1, \n fill = \"darkred\") +\n theme_minimal() +\n labs(\n x = \"date\",\n y = \"number of cases\",\n title = stringr::str_glue(\"Malaria cases - {plot_title_district}; {plot_title_facility}\"),\n subtitle = agegroup_title\n )\n \n \n \n}\n\nLet’s test it:\n\nplot_epicurve(malaria_data, \n district = \"Spring\", \n agegroup = \"malaria_rdt_0-4\", \n facility = \"Facility 1\")\n\n\n\n\n\n\n\n\nWith all the facilites in our data, it isn’t very clear which facilities correspond to which districts - and the end user won’t know either. This might make using the app quite unintuitive. For this reason, we should make the facility options in the UI change dynamically as the user changes the district - so one filters the other! Since we have so many variables that we’re using in the options, we might also want to generate some of our options for the ui in our global.R file from the data. For example, we can add this code chunk to global.R after we’ve read our data in:\n\nall_districts <- c(\"All\", unique(malaria_data$District))\n\n# data frame of location names by district\nfacility_list <- malaria_data %>%\n group_by(location_name, District) %>%\n summarise() %>% \n ungroup()\n\nLet’s look at them:\n\nall_districts\n\n[1] \"All\" \"Spring\" \"Bolo\" \"Dingo\" \"Barnard\"\n\n\n\nfacility_list\n\n# A tibble: 65 × 2\n location_name District\n <chr> <chr> \n 1 Facility 1 Spring \n 2 Facility 10 Bolo \n 3 Facility 11 Spring \n 4 Facility 12 Dingo \n 5 Facility 13 Bolo \n 6 Facility 14 Dingo \n 7 Facility 15 Barnard \n 8 Facility 16 Barnard \n 9 Facility 17 Barnard \n10 Facility 18 Bolo \n# ℹ 55 more rows\n\n\nWe can pass these new variables to the ui without any issue, since they are globally visible by both the server and the ui! Lets update our UI:\n\nui <- fluidPage(\n\n titlePanel(\"Malaria facility visualisation app\"),\n\n sidebarLayout(\n\n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = all_districts,\n selected = \"All\",\n multiple = FALSE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n # selector for facility\n selectInput(\n inputId = \"select_facility\",\n label = \"Select Facility\",\n choices = c(\"All\", facility_list$location_name),\n selected = \"All\"\n ),\n \n # horizontal line\n hr(),\n downloadButton(\n outputId = \"download_epicurve\",\n label = \"Download plot\"\n )\n\n ),\n\n mainPanel(\n # epicurve goes here\n plotOutput(\"malaria_epicurve\"),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n )\n \n )\n)\n\nNotice how we’re now passing variables for our choices instead of hard coding them in the ui! This might make our code more compact as well! Lastly, we’ll have to update the server. It will be easy to update our function to incorporate our new input (we just have to pass it as an argument to our new parameter), but we should remember we also want the ui to update dynamically when the user changes the selected district. It is important to understand here that we can change the parameters and behaviour of widgets while the app is running, but this needs to be done in the server. We need to understand a new way to output to the server to learn how to do this.\nThe functions we need to understand how to do this are known as observer functions, and are similar to reactive functions in how they behave. They have one key difference though:\n\nReactive functions do not directly affect outputs, and produce objects that can be seen in other locations in the server.\nObserver functions can affect server outputs, but do so via side effects of other functions. (They can also do other things, but this is their main function in practice).\n\nSimilar to reactive functions, there are two flavours of observer functions, and they are divided by the same logic that divides reactive functions:\n\nobserve() - this function runs whenever any inputs used inside of it change.\nobserveEvent() - this function runs when a user-specified input changes.\n\nWe also need to understand the shiny-provided functions that update widgets. These are fairly straightforward to run - they first take the session object from the server function (this doesn’t need to be understood for now), and then the inputId of the function to be changed. We then pass new versions of all parameters that are already taken by selectInput() - these will be automatically updated in the widget.\nLets look at an isolated example of how we could use this in our server. When the user changes the district, we want to filter our tibble of facilities by district, and update the choices to only reflect those that are available in that district (and an option for all facilities).\n\nobserve({\n \n if (input$select_district == \"All\") {\n new_choices <- facility_list$location_name\n } else {\n new_choices <- facility_list %>%\n filter(District == input$select_district) %>%\n pull(location_name)\n }\n \n new_choices <- c(\"All\", new_choices)\n \n updateSelectInput(session, \n inputId = \"select_facility\",\n choices = new_choices)\n \n})\n\nAnd that’s it! we can add it into our server, and that behaviour will now work. Here’s what our new server should look like:\n\nserver <- function(input, output, session) {\n \n malaria_plot <- reactive({\n plot_epicurve(malaria_data, \n district = input$select_district, \n agegroup = input$select_agegroup, \n facility = input$select_facility)\n })\n \n \n \n observe({\n \n if (input$select_district == \"All\") {\n new_choices <- facility_list$location_name\n } else {\n new_choices <- facility_list %>%\n filter(District == input$select_district) %>%\n pull(location_name)\n }\n \n new_choices <- c(\"All\", new_choices)\n \n updateSelectInput(session, \n inputId = \"select_facility\",\n choices = new_choices)\n \n })\n \n \n output$malaria_epicurve <- renderPlot(\n malaria_plot()\n )\n \n output$download_epicurve <- downloadHandler(\n \n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n malaria_plot(),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n \n \n}\n\n\n\n\n\n\n\n\n\n\n\n\nAdding another tab with a table\nNow we’ll move on to the last component we want to add to our app. We’ll want to separate our ui into two tabs, one of which will have an interactive table where the user can see the data they are making the epidemic curve with. To do this, we can use the packaged ui elements that come with shiny relevant to tabs. On a basic level, we can enclose most of our main panel in this general structure:\n\n# ... the rest of ui\n\nmainPanel(\n \n tabsetPanel(\n type = \"tabs\",\n tabPanel(\n \"Epidemic Curves\",\n ...\n ),\n tabPanel(\n \"Data\",\n ...\n )\n )\n)\n\nLets apply this to our ui. We also will want to use the DT package here - this is a great package for making interactive tables from pre-existing data. We can see it being used for DT::datatableOutput() in this example.\n\nui <- fluidPage(\n \n titlePanel(\"Malaria facility visualisation app\"),\n \n sidebarLayout(\n \n sidebarPanel(\n # selector for district\n selectInput(\n inputId = \"select_district\",\n label = \"Select district\",\n choices = all_districts,\n selected = \"All\",\n multiple = FALSE\n ),\n # selector for age group\n selectInput(\n inputId = \"select_agegroup\",\n label = \"Select age group\",\n choices = c(\n \"All ages\" = \"malaria_tot\",\n \"0-4 yrs\" = \"malaria_rdt_0-4\",\n \"5-14 yrs\" = \"malaria_rdt_5-14\",\n \"15+ yrs\" = \"malaria_rdt_15\"\n ), \n selected = \"All\",\n multiple = FALSE\n ),\n # selector for facility\n selectInput(\n inputId = \"select_facility\",\n label = \"Select Facility\",\n choices = c(\"All\", facility_list$location_name),\n selected = \"All\"\n ),\n \n # horizontal line\n hr(),\n downloadButton(\n outputId = \"download_epicurve\",\n label = \"Download plot\"\n )\n \n ),\n \n mainPanel(\n tabsetPanel(\n type = \"tabs\",\n tabPanel(\n \"Epidemic Curves\",\n plotOutput(\"malaria_epicurve\")\n ),\n tabPanel(\n \"Data\",\n DT::dataTableOutput(\"raw_data\")\n )\n ),\n br(),\n hr(),\n p(\"Welcome to the malaria facility visualisation app! To use this app, manipulate the widgets on the side to change the epidemic curve according to your preferences! To download a high quality image of the plot you've created, you can also download it with the download button. To see the raw data, use the raw data tab for an interactive form of the table. The data dictionary is as follows:\"),\n tags$ul(\n tags$li(tags$b(\"location_name\"), \" - the facility that the data were collected at\"),\n tags$li(tags$b(\"data_date\"), \" - the date the data were collected at\"),\n tags$li(tags$b(\"submitted_daate\"), \" - the date the data were submitted at\"),\n tags$li(tags$b(\"Province\"), \" - the province the data were collected at (all 'North' for this dataset)\"),\n tags$li(tags$b(\"District\"), \" - the district the data were collected at\"),\n tags$li(tags$b(\"age_group\"), \" - the age group the data were collected for (0-5, 5-14, 15+, and all ages)\"),\n tags$li(tags$b(\"cases_reported\"), \" - the number of cases reported for the facility/age group on the given date\")\n )\n \n \n )\n )\n)\n\nNow our app is arranged into tabs! Lets make the necessary edits to the server as well. Since we dont need to manipulate our dataset at all before we render it this is actually very simple - we just render the malaria_data dataset via DT::renderDT() to the ui!\n\nserver <- function(input, output, session) {\n \n malaria_plot <- reactive({\n plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility)\n })\n \n \n \n observe({\n \n if (input$select_district == \"All\") {\n new_choices <- facility_list$location_name\n } else {\n new_choices <- facility_list %>%\n filter(District == input$select_district) %>%\n pull(location_name)\n }\n \n new_choices <- c(\"All\", new_choices)\n \n updateSelectInput(session, inputId = \"select_facility\",\n choices = new_choices)\n \n })\n \n \n output$malaria_epicurve <- renderPlot(\n malaria_plot()\n )\n \n output$download_epicurve <- downloadHandler(\n \n filename = function() {\n stringr::str_glue(\"malaria_epicurve_{input$select_district}.png\")\n },\n \n content = function(file) {\n ggsave(file, \n malaria_plot(),\n width = 8, height = 5, dpi = 300)\n }\n \n )\n \n # render data table to ui\n output$raw_data <- DT::renderDT(\n malaria_data\n )\n \n \n}", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3915,7 +3915,7 @@ "href": "new_pages/shiny_basics.html#sharing-shiny-apps", "title": "43  Dashboards with Shiny", "section": "43.7 Sharing shiny apps", - "text": "43.7 Sharing shiny apps\nNow that you’ve developed your app, you probably want to share it with others - this is the main advantage of shiny after all! We can do this by sharing the code directly, or we could publish on a server. If we share the code, others will be able to see what you’ve done and build on it, but this will negate one of the main advantages of shiny - it can eliminate the need for end-users to maintain an R installation. For this reason, if you’re sharing your app with users who are not comfortable with R, it is much easier to share an app that has been published on a server.\nIf you’d rather share the code, you could make a .zip file of the app, or better yet, publish your app on github and add collaborators. You can refer to the section on github for further information here.\nHowever, if we’re publishing the app online, we need to do a little more work. Ultimately, we want your app to be able to be accessed via a web URL so others can get quick and easy access to it. Unfortunately, to publish you app on a server, you need to have access to a server to publish it on! There are a number of hosting options when it comes to this:\n\nshinyapps.io: this is the easiest place to publish shiny apps, as it has the smallest amount of configuration work needed, and has some free, but limited licenses.\nRStudio Connect: this is a far more powerful version of an R server, that can perform many operations, including publishing shiny apps. It is however, harder to use, and less recommended for first-time users.\n\nFor the purposes of this document, we will use shinyapps.io, since it is easier for first time users. You can make a free account here to start - there are also different price plans for server licesnses if needed. The more users you expect to have, the more expensive your price plan may have to be, so keep this under consideration. If you’re looking to create something for a small set of individuals to use, a free license may be perfectly suitable, but a public facing app may need more licenses.\nFirst we should make sure our app is suitable for publishing on a server. In your app, you should restart your R session, and ensure that it runs without running any extra code. This is important, as an app that requires package loading, or data reading not defined in your app code won’t run on a server. Also note that you can’t have any explicit file paths in your app - these will be invalid in the server setting - using the here package solves this issue very well. Finally, if you’re reading data from a source that requires user-authentication, such as your organisation’s servers, this will not generally work on a server. You will need to liase with your IT department to figure out how to whitelist the shiny server here.\nsigning up for account\nOnce you have your account, you can navigate to the tokens page under Accounts. Here you will want to add a new token - this will be used to deploy your app.\nFrom here, you should note that the url of your account will reflect the name of your app - so if your app is called my_app, the url will be appended as xxx.io/my_app/. Choose your app name wisely! Now that you are all ready, click deploy - if successful this will run your app on the web url you chose!\nsomething on making apps in documents?", + "text": "43.7 Sharing shiny apps\nNow that you’ve developed your app, you probably want to share it with others - this is the main advantage of shiny after all! We can do this by sharing the code directly, or we could publish on a server. If we share the code, others will be able to see what you’ve done and build on it, but this will negate one of the main advantages of shiny - it can eliminate the need for end-users to maintain an R installation. For this reason, if you’re sharing your app with users who are not comfortable with R, it is much easier to share an app that has been published on a server.\nIf you’d rather share the code, you could make a .zip file of the app, or better yet, publish your app on github and add collaborators. You can refer to the section on github for further information here.\nHowever, if we’re publishing the app online, we need to do a little more work. Ultimately, we want your app to be able to be accessed via a web URL so others can get quick and easy access to it. Unfortunately, to publish you app on a server, you need to have access to a server to publish it on! There are a number of hosting options when it comes to this:\n\nshinyapps.io: this is the easiest place to publish shiny apps, as it has the smallest amount of configuration work needed, and has some free, but limited licenses.\nRStudio Connect: this is a far more powerful version of an R server, that can perform many operations, including publishing shiny apps. It is however, harder to use, and less recommended for first-time users.\n\nFor the purposes of this document, we will use shinyapps.io, since it is easier for first time users. You can make a free account here to start - there are also different price plans for server licesnses if needed. The more users you expect to have, the more expensive your price plan may have to be, so keep this under consideration. If you’re looking to create something for a small set of individuals to use, a free license may be perfectly suitable, but a public facing app may need more licenses.\nFirst we should make sure our app is suitable for publishing on a server. In your app, you should restart your R session, and ensure that it runs without running any extra code. This is important, as an app that requires package loading, or data reading not defined in your app code won’t run on a server. Also note that you can’t have any explicit file paths in your app - these will be invalid in the server setting - using the here package solves this issue very well. Finally, if you’re reading data from a source that requires user-authentication, such as your organisation’s servers, this will not generally work on a server. You will need to liase with your IT department to figure out how to whitelist the shiny server here.\nOnce you have your account, you can navigate to the tokens page under Accounts. Here you will want to add a new token - this will be used to deploy your app.\nFrom here, you should note that the url of your account will reflect the name of your app - so if your app is called my_app, the url will be appended as xxx.io/my_app/. Choose your app name wisely! Now that you are all ready, click deploy - if successful this will run your app on the web url you chose!", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3936,8 +3936,8 @@ "objectID": "new_pages/shiny_basics.html#recommended-extension-packages", "href": "new_pages/shiny_basics.html#recommended-extension-packages", "title": "43  Dashboards with Shiny", - "section": "43.9 Recommended extension packages", - "text": "43.9 Recommended extension packages\nThe following represents a selection of high quality shiny extensions that can help you get a lot more out of shiny. In no particular order:\n\nshinyWidgets - this package gives you many many more widgets that can be used in your app. Run shinyWidgets::shinyWidgetsGallery() to see a selection of available widgets with this package. See examples here\nshinyjs - this is an excellent package that gives the user the ability to greatly extend shiny’s utility via a series of javascript. The applications of this package range from very simple to highly advanced, but you might want to first use it to manipulate the ui in simple ways, like hiding/showing elements, or enabling/disabling buttons. Find out more here\nshinydashboard - this package massively expands the available ui that can be used in shiny, specifically letting the user create a complex dashboard with a variety of complex layouts. See more here\nshinydashboardPlus - get even more features out of the shinydashboard framework! See more here\nshinythemes - change the default css theme for your shiny app with a wide range of preset templates! See more here\n\nThere are also a number of packages that can be used to create interactive outputs that are shiny compatible.\n\nDT is semi-incorporated into base-shiny, but provides a great set of functions to create interactive tables.\nplotly is a package for creating interactive plots that the user can manipulate in app. You can also convert your plot to interactive versions via plotly::ggplotly()! As alternatives, dygraphs and highcharter are also excellent.", + "section": "43.8 Recommended extension packages", + "text": "43.8 Recommended extension packages\nThe following represents a selection of high quality shiny extensions that can help you get a lot more out of shiny. In no particular order:\n\nshinyWidgets - this package gives you many many more widgets that can be used in your app. Run shinyWidgets::shinyWidgetsGallery() to see a selection of available widgets with this package. See examples here\nshinyjs - this is an excellent package that gives the user the ability to greatly extend shiny’s utility via a series of javascript. The applications of this package range from very simple to highly advanced, but you might want to first use it to manipulate the ui in simple ways, like hiding/showing elements, or enabling/disabling buttons. Find out more here\nshinydashboard - this package massively expands the available ui that can be used in shiny, specifically letting the user create a complex dashboard with a variety of complex layouts. See more here\nshinydashboardPlus - get even more features out of the shinydashboard framework! See more here\nshinythemes - change the default css theme for your shiny app with a wide range of preset templates! See more here\n\nThere are also a number of packages that can be used to create interactive outputs that are shiny compatible.\n\nDT is semi-incorporated into base-shiny, but provides a great set of functions to create interactive tables.\nplotly is a package for creating interactive plots that the user can manipulate in app. You can also convert your plot to interactive versions via plotly::ggplotly()! As alternatives, dygraphs and highcharter are also excellent.", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3947,8 +3947,8 @@ "objectID": "new_pages/shiny_basics.html#recommended-resources", "href": "new_pages/shiny_basics.html#recommended-resources", "title": "43  Dashboards with Shiny", - "section": "43.10 Recommended resources", - "text": "43.10 Recommended resources", + "section": "43.9 Recommended resources", + "text": "43.9 Recommended resources\nSo far, we’ve covered a lot of aspects of shiny, and have barely scratched the surface of what is on offer for shiny. While this guide serves as an introduction, there is loads more to learn to fully understand shiny. You should start making apps and gradually add more and more functionality.\nWelcome to Shiny (Tutorial) Introducing Shiny (Tutorial) A Gentle Introduction to Shiny (Bookdown chapter)", "crumbs": [ "Reports and dashboards", "43  Dashboards with Shiny" @@ -3957,452 +3957,452 @@ { "objectID": "new_pages/writing_functions.html", "href": "new_pages/writing_functions.html", - "title": "44  Writing functions", + "title": "45  Writing functions", "section": "", - "text": "44.1 Preparation", + "text": "45.1 Preparation", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#preparation", "href": "new_pages/writing_functions.html#preparation", - "title": "44  Writing functions", + "title": "45  Writing functions", "section": "", "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instructions in the Download book and data page. The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.\nWe will also use in the last part of this page some data on H7N9 flu from 2013.", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#functions", "href": "new_pages/writing_functions.html#functions", - "title": "44  Writing functions", - "section": "44.2 Functions", - "text": "44.2 Functions\nFunctions are helpful in programming since they allow to make codes easier to understand, somehow shorter and less prone to errors (given there were no errors in the function itself).\nIf you have come so far to this handbook, it means you have came across endless functions since in R, every operation is a function call +, for, if, [, $, { …. For example x + y is the same as'+'(x, y)\nR is one the languages that offers the most possibility to work with functions and give enough tools to the user to easily write them. We should not think about functions as fixed at the top or at the end of the programming chain, R offers the possibility to use them as if they were vectors and even to use them inside other functions, lists…\nLot of very advanced resources on functional programming exist and we will only give here an insight to help you start with functional programming with short practical examples. You are then encouraged to visit the links on references to read more about it.", + "title": "45  Writing functions", + "section": "45.2 Functions", + "text": "45.2 Functions\nFunctions are helpful in programming since they allow to make codes easier to understand, somehow shorter and less prone to errors (given there were no errors in the function itself).\nIf you have come so far to this handbook, it means you have came across endless functions since in R, every operation is a function call +, for, if, [, $, { …. For example x + y is the same as'+'(x, y).\nR is one of the programming languages that offers the most possibility to work with functions and give enough tools to the user to easily write them. We should not think about functions as fixed at the top or at the end of the programming chain, R offers the possibility to use them as if they were vectors and even to use them inside other functions.\nLot of very advanced resources on functional programming exist and we will only give here an insight to help you start with functional programming with short practical examples. You are then encouraged to visit the links on references to read more about it.", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#why-would-you-use-a-function", "href": "new_pages/writing_functions.html#why-would-you-use-a-function", - "title": "44  Writing functions", - "section": "44.3 Why would you use a function?", - "text": "44.3 Why would you use a function?\nBefore answering this question, it is important to note that you have already had tips to get to write your very first R functions in the page on Iteration, loops, and lists of this handbook. In fact, use of “if/else” and loops is often a core part of many of our functions since they easily help to either broaden the application of our code allowing multiple conditions or to iterate codes for repeating tasks.\n\nI am repeating multiple times the same block of code to apply it to a different variable or data?\nGetting rid of it will it substantially shorten my overall code and make it run quicker?\nIs it possible that the code I have written is used again but with a different value at many places of the code?\n\nIf the answer to one of the previous questions is “YES”, then you probably need to write a function", + "title": "45  Writing functions", + "section": "45.3 Why would you use a function?", + "text": "45.3 Why would you use a function?\nBefore answering this question, it is important to note that you have already had tips to get to write your very first R functions in the page on Iteration, loops, and lists of this handbook. In fact, use of “if/else” and loops is often a core part of many of our functions since they easily help to either broaden the application of our code allowing multiple conditions or to iterate codes for repeating tasks.\n\nI am repeating multiple times the same block of code to apply it to a different variable or data?\nGetting rid of it will it substantially shorten my overall code and make it run quicker?\nIs it possible that the code I have written is used again but with a different value at many places of the code?\n\nIf the answer to one of the previous questions is “YES”, then you probably need to write a function.", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#how-does-r-build-functions", "href": "new_pages/writing_functions.html#how-does-r-build-functions", - "title": "44  Writing functions", - "section": "44.4 How does R build functions?", - "text": "44.4 How does R build functions?\nFunctions in R have three main components:\n\nthe formals() which is the list of arguments which controls how we can call the function\nthe body() that is the code inside the function i.e. within the brackets or following the parenthesis depending on how we write it\n\nand,\n\nthe environment() which will help locate the function’s variables and determines how the function finds value.\n\nOnce you have created your function, you can verify each of these components by calling the function associated.", + "title": "45  Writing functions", + "section": "45.4 How does R build functions?", + "text": "45.4 How does R build functions?\nFunctions in R have three main components:\n\nthe formals() which is the list of arguments which controls how we can call the function.\nthe body() that is the code inside the function i.e. within the brackets or following the parenthesis depending on how we write it.\n\nAnd,\n\nthe environment() which will help locate the function’s variables and determines how the function finds value.\n\nOnce you have created your function, you can verify each of these components by calling the function associated.", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#basic-syntax-and-structure", "href": "new_pages/writing_functions.html#basic-syntax-and-structure", - "title": "44  Writing functions", - "section": "44.5 Basic syntax and structure", - "text": "44.5 Basic syntax and structure\n\nA function will need to be named properly so that its job is easily understandable as soon as we read its name. Actually this is already the case with majority of the base R architecture. Functions like mean(), print(), summary() have names that are very straightforward\nA function will need arguments, such as the data to work on and other objects that can be static values among other options\nAnd finally a function will give an output based on its core task and the arguments it has been given. Usually we will use the built-in functions as print(), return()… to produce the output. The output can be a logical value, a number, a character, a data frame…in short any kind of R object.\n\nBasically this is the composition of a function:\n\nfunction_name <- function(argument_1, argument_2, argument_3){\n \n function_task\n \n return(output)\n}\n\nWe can create our first function that will be called contain_covid19().\n\ncontain_covid19 <- function(barrier_gest, wear_mask, get_vaccine){\n \n if(barrier_gest == \"yes\" & wear_mask == \"yes\" & get_vaccine == \"yes\" ) \n \n return(\"success\")\n \n else(\"please make sure all are yes, this pandemic has to end!\")\n}\n\nWe can then verify the components of our newly created function.\n\nformals(contain_covid19)\n\n$barrier_gest\n\n\n$wear_mask\n\n\n$get_vaccine\n\nbody(contain_covid19)\n\n{\n if (barrier_gest == \"yes\" & wear_mask == \"yes\" & get_vaccine == \n \"yes\") \n return(\"success\")\n else (\"please make sure all are yes, this pandemic has to end!\")\n}\n\nenvironment(contain_covid19)\n\n<environment: R_GlobalEnv>\n\n\nNow we will test our function. To call our written function, you use it as you use all R functions i.e by writing the function name and adding the required arguments.\n\ncontain_covid19(barrier_gest = \"yes\", wear_mask = \"yes\", get_vaccine = \"yes\")\n\n[1] \"success\"\n\n\nWe can write again the name of each argument for precautionary reasons. But without specifying them, the code should work since R has in memory the positioning of each argument. So as long as you put the values of the arguments in the correct order, you can skip writing the arguments names when calling the functions.\n\ncontain_covid19(\"yes\", \"yes\", \"yes\")\n\n[1] \"success\"\n\n\nThen let’s look what happens if one of the values is \"no\" or not \"yes\".\n\ncontain_covid19(barrier_gest = \"yes\", wear_mask = \"yes\", get_vaccine = \"no\")\n\n[1] \"please make sure all are yes, this pandemic has to end!\"\n\n\nIf we provide an argument that is not recognized, we get an error:\n\ncontain_covid19(barrier_gest = \"sometimes\", wear_mask = \"yes\", get_vaccine = \"no\")\n\nError in contain_covid19(barrier_gest = \"sometimes\", wear_mask = \"yes\", : could not find function \"contain_covid19\"\nNOTE: Some functions (most of time very short and straightforward) may not need a name and can be used directly on a line of code or inside another function to do quick task. They are called anonymous functions .\nFor instance below is a first anonymous function that keeps only character variables the dataset.\n\nlinelist %>% \n dplyr::slice_head(n=10) %>% #equivalent to R base \"head\" function and that return first n observation of the dataset\n select(function(x) is.character(x)) \n\n\n\n\n\n\n\nThen another function that selects every second observation of our dataset (may be relevant when we have longitudinal data with many records per patient for instance after having ordered by date or visit). In this case, the proper function writing outside dplyr would be function (x) (x%%2 == 0) to apply to the vector containing all row numbers.\n\nlinelist %>% \n slice_head(n=20) %>% \n tibble::rownames_to_column() %>% # add indices of each obs as rownames to clearly see the final selection\n filter(row_number() %%2 == 0)\n\n\n\n\n\n\n\nA possible base R code for the same task would be:\n\nlinelist_firstobs <- head(linelist, 20)\n\nlinelist_firstobs[base::Filter(function(x) (x%%2 == 0), seq(nrow(linelist_firstobs))),]\n\n\n\n\n\n\n\nCAUTION: Though it is true that using functions can help us with our code, it can nevertheless be time consuming to write some functions or to fix one if it has not been thought thoroughly, written adequately and is returning errors as a result. For this reason it is often recommended to first write the R code, make sure it does what we intend it to do, and then transform it into a function with its three main components as listed above.", + "title": "45  Writing functions", + "section": "45.5 Basic syntax and structure", + "text": "45.5 Basic syntax and structure\n\nA function will need to be named properly so that its job is easily understandable as soon as we read its name. Actually this is already the case with majority of the base R architecture. Functions like mean(), print(), summary() have names that are very straightforward.\nA function will need arguments, such as the data to work on and other objects that can be static values among other options.\nAnd finally a function will give an output based on its core task and the arguments it has been given. Usually we will use the built-in functions as print(), return()… to produce the output. The output can be a logical value, a number, a character, a data frame…in short any kind of R object.\n\nBasically this is the composition of a function:\n\nfunction_name <- function(argument_1, argument_2, argument_3){\n \n function_task\n \n return(output)\n}\n\nWe can create our first function that will be called contain_covid19().\n\ncontain_covid19 <- function(barrier_gest, wear_mask, get_vaccine){\n if(barrier_gest == \"yes\" & \n wear_mask == \"yes\" & \n get_vaccine == \"yes\" ) \n return(\"success\")\n else(\"please make sure all are yes, we need as much help containing COVID as we can get!\")\n}\n\nWe can then verify the components of our newly created function.\n\nformals(contain_covid19)\n\n$barrier_gest\n\n\n$wear_mask\n\n\n$get_vaccine\n\nbody(contain_covid19)\n\n{\n if (barrier_gest == \"yes\" & wear_mask == \"yes\" & get_vaccine == \n \"yes\") \n return(\"success\")\n else (\"please make sure all are yes, we need as much help containing COVID as we can get!\")\n}\n\nenvironment(contain_covid19)\n\n<environment: R_GlobalEnv>\n\n\nNow we will test our function. To call our written function, you use it as you use all R functions i.e by writing the function name and adding the required arguments.\n\ncontain_covid19(barrier_gest = \"yes\", wear_mask = \"yes\", get_vaccine = \"yes\")\n\n[1] \"success\"\n\n\nWe can write again the name of each argument for precautionary reasons. But without specifying them, the code should work since R has in memory the positioning of each argument. So as long as you put the values of the arguments in the correct order, you can skip writing the arguments names when calling the functions.\n\ncontain_covid19(\"yes\", \"yes\", \"yes\")\n\n[1] \"success\"\n\n\nThen let’s look what happens if one of the values is \"no\" or not \"yes\".\n\ncontain_covid19(barrier_gest = \"yes\", wear_mask = \"yes\", get_vaccine = \"no\")\n\n[1] \"please make sure all are yes, we need as much help containing COVID as we can get!\"\n\n\nIf we provide an argument that is not recognized, we get an error:\n\ncontain_covid19(barrier_gest = \"sometimes\", wear_mask = \"yes\", get_vaccine = \"no\")\n\nError in contain_covid19(barrier_gest = \"sometimes\", wear_mask = \"yes\", : could not find function \"contain_covid19\"\nNOTE: Some functions (most of time very short and straightforward) may not need a name and can be used directly on a line of code or inside another function to do quick task. They are called anonymous functions .\nFor instance below is a first anonymous function that keeps only character variables the dataset.\n\nlinelist %>% \n dplyr::slice_head(n=10) %>% #equivalent to R base \"head\" function and that return first n observation of the dataset\n select(function(x) is.character(x)) \n\n\n\n\n\n\n\nThen another function that selects every second observation of our dataset (may be relevant when we have longitudinal data with many records per patient for instance after having ordered by date or visit). In this case, the proper function writing outside dplyr would be function (x) (x%%2 == 0) to apply to the vector containing all row numbers.\n\nlinelist %>% \n slice_head(n=20) %>% \n tibble::rownames_to_column() %>% # add indices of each obs as rownames to clearly see the final selection\n filter(row_number() %%2 == 0)\n\n\n\n\n\n\n\nA possible base R code for the same task would be:\n\nlinelist_firstobs <- head(linelist, 20)\n\nlinelist_firstobs[base::Filter(function(x) (x%%2 == 0), seq(nrow(linelist_firstobs))),]\n\n\n\n\n\n\n\nCAUTION: Though it is true that using functions can help us with our code, it can nevertheless be time consuming to write some functions or to fix one if it has not been thought thoroughly, written adequately and is returning errors as a result. For this reason it is often recommended to first write the R code, make sure it does what we intend it to do, and then transform it into a function with its three main components as listed above.", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#examples", "href": "new_pages/writing_functions.html#examples", - "title": "44  Writing functions", - "section": "44.6 Examples", - "text": "44.6 Examples\n\nReturn proportion tables for several columns\nYes, we already have nice functions in many packages allowing to summarize information in a very easy and nice way. But we will still try to make our own, in our first steps to getting used to writing functions.\nIn this example we want to show how writing a simple function would avoid you copy-pasting the same code multiple times.\n\nproptab_multiple <- function(my_data, var_to_tab){\n \n #print the name of each variable of interest before doing the tabulation\n print(var_to_tab)\n\n with(my_data,\n rbind( #bind the results of the two following function by row\n #tabulate the variable of interest: gives only numbers\n table(my_data[[var_to_tab]], useNA = \"no\"),\n #calculate the proportions for each variable of interest and round the value to 2 decimals\n round(prop.table(table(my_data[[var_to_tab]]))*100,2)\n )\n )\n}\n\n\nproptab_multiple(linelist, \"gender\")\n\n[1] \"gender\"\n\n\n f m\n[1,] 2807.00 2803.00\n[2,] 50.04 49.96\n\nproptab_multiple(linelist, \"age_cat\")\n\n[1] \"age_cat\"\n\n\n 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+\n[1,] 1095.00 1095.00 941.00 743.00 1073.00 754 95.00 6.0\n[2,] 18.87 18.87 16.22 12.81 18.49 13 1.64 0.1\n\nproptab_multiple(linelist, \"outcome\")\n\n[1] \"outcome\"\n\n\n Death Recover\n[1,] 2582.00 1983.00\n[2,] 56.56 43.44\n\n\nTIP: As shown above, it is very important to comment your functions as you would do for the general programming. Bear in mind that a function’s aim is to make a code ready to read, shorter and more efficient. Then one should be able to understand what the function does just by reading its name and should have more details reading the comments.\nA second option is to use this function in another one via a loop to make the process at once:\n\nfor(var_to_tab in c(\"gender\",\"age_cat\", \"outcome\")){\n \n print(proptab_multiple(linelist, var_to_tab))\n \n}\n\n[1] \"gender\"\n f m\n[1,] 2807.00 2803.00\n[2,] 50.04 49.96\n[1] \"age_cat\"\n 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+\n[1,] 1095.00 1095.00 941.00 743.00 1073.00 754 95.00 6.0\n[2,] 18.87 18.87 16.22 12.81 18.49 13 1.64 0.1\n[1] \"outcome\"\n Death Recover\n[1,] 2582.00 1983.00\n[2,] 56.56 43.44\n\n\nA simpler way could be using the base R “apply” instead of a “for loop” as expressed below:\nTIP: R is often defined as a functional programming language and almost anytime you run a line of code you are using some built-in functions. A good habit to be more comfortable with writing functions is to often have an internal look at how the basic functions you are using daily are built. The shortcut to do so is selecting the function name and then clicking onCtrl+F2 or fn+F2 or Cmd+F2 (depending on your computer) .", + "title": "45  Writing functions", + "section": "45.6 Examples", + "text": "45.6 Examples\n\nReturn proportion tables for several columns\nYes, we already have nice functions in many packages allowing to summarize information in a very easy and nice way. But we will still try to make our own, in our first steps to getting used to writing functions.\nIn this example we want to show how writing a simple function would avoid you copy-pasting the same code multiple times.\n\nproptab_multiple <- function(my_data, var_to_tab){\n \n #print the name of each variable of interest before doing the tabulation\n print(var_to_tab)\n\n with(my_data,\n rbind( #bind the results of the two following function by row\n #tabulate the variable of interest: gives only numbers\n table(my_data[[var_to_tab]], useNA = \"no\"),\n #calculate the proportions for each variable of interest and round the value to 2 decimals\n round(prop.table(table(my_data[[var_to_tab]]))*100,2)\n )\n )\n}\n\n\nproptab_multiple(linelist, \"gender\")\n\n[1] \"gender\"\n\n\n f m\n[1,] 2807.00 2803.00\n[2,] 50.04 49.96\n\nproptab_multiple(linelist, \"age_cat\")\n\n[1] \"age_cat\"\n\n\n 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+\n[1,] 1095.00 1095.00 941.00 743.00 1073.00 754 95.00 6.0\n[2,] 18.87 18.87 16.22 12.81 18.49 13 1.64 0.1\n\nproptab_multiple(linelist, \"outcome\")\n\n[1] \"outcome\"\n\n\n Death Recover\n[1,] 2582.00 1983.00\n[2,] 56.56 43.44\n\n\nTIP: As shown above, it is very important to comment your functions as you would do for the general programming. Bear in mind that a function’s aim is to make a code ready to read, shorter and more efficient. Then one should be able to understand what the function does just by reading its name and should have more details reading the comments.\nA second option is to use this function in another one via a loop to make the process at once:\n\nfor(var_to_tab in c(\"gender\",\"age_cat\", \"outcome\")){\n \n print(proptab_multiple(linelist, var_to_tab))\n \n}\n\n[1] \"gender\"\n f m\n[1,] 2807.00 2803.00\n[2,] 50.04 49.96\n[1] \"age_cat\"\n 0-4 5-9 10-14 15-19 20-29 30-49 50-69 70+\n[1,] 1095.00 1095.00 941.00 743.00 1073.00 754 95.00 6.0\n[2,] 18.87 18.87 16.22 12.81 18.49 13 1.64 0.1\n[1] \"outcome\"\n Death Recover\n[1,] 2582.00 1983.00\n[2,] 56.56 43.44\n\n\nA simpler way could be using the base R “apply” instead of a “for loop” as expressed below:\nTIP: R is often defined as a functional programming language and almost anytime you run a line of code you are using some built-in functions. A good habit to be more comfortable with writing functions is to often have an internal look at how the basic functions you are using daily are built. The shortcut to do so is selecting the function name and then clicking onCtrl+F2 or fn+F2 or Cmd+F2 (depending on your computer) .", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#using-purrr-writing-functions-that-can-be-iteratively-applied", "href": "new_pages/writing_functions.html#using-purrr-writing-functions-that-can-be-iteratively-applied", - "title": "44  Writing functions", - "section": "44.7 Using purrr: writing functions that can be iteratively applied", - "text": "44.7 Using purrr: writing functions that can be iteratively applied\n\nModify class of multiple columns in a dataset\nLet’s say many character variables in the original linelist data need to be changes to “factor” for analysis and plotting purposes. Instead of repeating the step several times, we can just use lapply() to do the transformation of all variables concerned on a single line of code.\nCAUTION: lapply() returns a list, thus its use may require an additional modification as a last step.\nThe same step can be done using map_if() function from the purrr package\n\nlinelist_factor2 <- linelist %>%\n purrr::map_if(is.character, as.factor)\n\n\nlinelist_factor2 %>%\n glimpse()\n\nList of 30\n $ case_id : Factor w/ 5888 levels \"00031d\",\"00086d\",..: 2134 3022 396 4203 3084 4347 179 1241 5594 430 ...\n $ generation : num [1:5888] 4 4 2 3 3 3 4 4 4 4 ...\n $ date_infection : Date[1:5888], format: \"2014-05-08\" NA ...\n $ date_onset : Date[1:5888], format: \"2014-05-13\" \"2014-05-13\" ...\n $ date_hospitalisation: Date[1:5888], format: \"2014-05-15\" \"2014-05-14\" ...\n $ date_outcome : Date[1:5888], format: NA \"2014-05-18\" ...\n $ outcome : Factor w/ 2 levels \"Death\",\"Recover\": NA 2 2 NA 2 2 2 1 2 1 ...\n $ gender : Factor w/ 2 levels \"f\",\"m\": 2 1 2 1 2 1 1 1 2 1 ...\n $ age : num [1:5888] 2 3 56 18 3 16 16 0 61 27 ...\n $ age_unit : Factor w/ 2 levels \"months\",\"years\": 2 2 2 2 2 2 2 2 2 2 ...\n $ age_years : num [1:5888] 2 3 56 18 3 16 16 0 61 27 ...\n $ age_cat : Factor w/ 8 levels \"0-4\",\"5-9\",\"10-14\",..: 1 1 7 4 1 4 4 1 7 5 ...\n $ age_cat5 : Factor w/ 18 levels \"0-4\",\"5-9\",\"10-14\",..: 1 1 12 4 1 4 4 1 13 6 ...\n $ hospital : Factor w/ 6 levels \"Central Hospital\",..: 4 3 6 5 2 5 3 3 3 3 ...\n $ lon : num [1:5888] -13.2 -13.2 -13.2 -13.2 -13.2 ...\n $ lat : num [1:5888] 8.47 8.45 8.46 8.48 8.46 ...\n $ infector : Factor w/ 2697 levels \"00031d\",\"002e6c\",..: 2594 NA NA 2635 180 1799 1407 195 NA NA ...\n $ source : Factor w/ 2 levels \"funeral\",\"other\": 2 NA NA 2 2 2 2 2 NA NA ...\n $ wt_kg : num [1:5888] 27 25 91 41 36 56 47 0 86 69 ...\n $ ht_cm : num [1:5888] 48 59 238 135 71 116 87 11 226 174 ...\n $ ct_blood : num [1:5888] 22 22 21 23 23 21 21 22 22 22 ...\n $ fever : Factor w/ 2 levels \"no\",\"yes\": 1 NA NA 1 1 1 NA 1 1 1 ...\n $ chills : Factor w/ 2 levels \"no\",\"yes\": 1 NA NA 1 1 1 NA 1 1 1 ...\n $ cough : Factor w/ 2 levels \"no\",\"yes\": 2 NA NA 1 2 2 NA 2 2 2 ...\n $ aches : Factor w/ 2 levels \"no\",\"yes\": 1 NA NA 1 1 1 NA 1 1 1 ...\n $ vomit : Factor w/ 2 levels \"no\",\"yes\": 2 NA NA 1 2 2 NA 2 2 1 ...\n $ temp : num [1:5888] 36.8 36.9 36.9 36.8 36.9 37.6 37.3 37 36.4 35.9 ...\n $ time_admission : Factor w/ 1072 levels \"00:10\",\"00:29\",..: NA 308 746 415 514 589 609 297 409 387 ...\n $ bmi : num [1:5888] 117.2 71.8 16.1 22.5 71.4 ...\n $ days_onset_hosp : num [1:5888] 2 1 2 2 1 1 2 1 1 2 ...\n\n\n\n\nIteratively produce graphs for different levels of a variable\nWe will produce here pie chart to look at the distribution of patient’s outcome in China during the H7N9 outbreak for each province. Instead of repeating the code for each of them, we will just apply a function that we will create.\n\n#precising options for the use of highchart\noptions(highcharter.theme = highcharter::hc_theme_smpl(tooltip = list(valueDecimals = 2)))\n\n\n#create a function called \"chart_outcome_province\" that takes as argument the dataset and the name of the province for which to plot the distribution of the outcome.\n\nchart_outcome_province <- function(data_used, prov){\n \n tab_prov <- data_used %>% \n filter(province == prov,\n !is.na(outcome))%>% \n group_by(outcome) %>% \n count() %>%\n adorn_totals(where = \"row\") %>% \n adorn_percentages(denominator = \"col\", )%>%\n mutate(\n perc_outcome= round(n*100,2))\n \n \n tab_prov %>%\n filter(outcome != \"Total\") %>% \n highcharter::hchart(\n \"pie\", hcaes(x = outcome, y = perc_outcome),\n name = paste0(\"Distibution of the outcome in:\", prov)\n )\n \n}\n\nchart_outcome_province(flu_china, \"Shanghai\")\n\n\n\n\nchart_outcome_province(flu_china,\"Zhejiang\")\n\n\n\n\nchart_outcome_province(flu_china,\"Jiangsu\")\n\n\n\n\n\n\n\nIteratively produce tables for different levels of a variable\nHere we will create three indicators to summarize in a table and we would like to produce this table for each of the provinces. Our indicators are the delay between onset and hospitalization, the percentage of recovery and the median age of cases.\n\nindic_1 <- flu_china %>% \n group_by(province) %>% \n mutate(\n date_hosp= strptime(date_of_hospitalisation, format = \"%m/%d/%Y\"),\n date_ons= strptime(date_of_onset, format = \"%m/%d/%Y\"), \n delay_onset_hosp= as.numeric(date_hosp - date_ons)/86400,\n mean_delay_onset_hosp = round(mean(delay_onset_hosp, na.rm=TRUE ), 0)) %>%\n select(province, mean_delay_onset_hosp) %>% \n distinct()\n \n\nindic_2 <- flu_china %>% \n filter(!is.na(outcome)) %>% \n group_by(province, outcome) %>% \n count() %>%\n pivot_wider(names_from = outcome, values_from = n) %>% \n adorn_totals(where = \"col\") %>% \n mutate(\n perc_recovery= round((Recover/Total)*100,2))%>% \n select(province, perc_recovery)\n \n \n \nindic_3 <- flu_china %>% \n group_by(province) %>% \n mutate(\n median_age_cases = median(as.numeric(age), na.rm = TRUE)\n ) %>% \n select(province, median_age_cases) %>% \n distinct()\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `median_age_cases = median(as.numeric(age), na.rm = TRUE)`.\nℹ In group 11: `province = \"Shanghai\"`.\nCaused by warning in `median()`:\n! NAs introduced by coercion\n\n#join the three indicator datasets\n\ntable_indic_all <- indic_1 %>% \n dplyr::left_join(indic_2, by = \"province\") %>% \n left_join(indic_3, by = \"province\")\n\n\n#print the indicators in a flextable\n\n\nprint_indic_prov <- function(table_used, prov){\n \n #first transform a bit the dataframe for printing ease\n indic_prov <- table_used %>%\n filter(province==prov) %>%\n pivot_longer(names_to = \"Indicateurs\", cols = 2:4) %>% \n mutate( indic_label = factor(Indicateurs,\n levels= c(\"mean_delay_onset_hosp\",\"perc_recovery\",\"median_age_cases\"),\n labels=c(\"Mean delay onset-hosp\",\"Percentage of recovery\", \"Median age of the cases\"))\n ) %>% \n ungroup(province) %>% \n select(indic_label, value)\n \n\n tab_print <- flextable(indic_prov) %>%\n theme_vanilla() %>% \n flextable::fontsize(part = \"body\", size = 10) \n \n \n tab_print <- tab_print %>% \n autofit() %>%\n set_header_labels( \n indic_label= \"Indicateurs\", value= \"Estimation\") %>%\n flextable::bg( bg = \"darkblue\", part = \"header\") %>%\n flextable::bold(part = \"header\") %>%\n flextable::color(color = \"white\", part = \"header\") %>% \n add_header_lines(values = paste0(\"Indicateurs pour la province de: \", prov)) %>% \nbold(part = \"header\")\n \n tab_print <- set_formatter_type(tab_print,\n fmt_double = \"%.2f\",\n na_str = \"-\")\n\ntab_print \n \n}\n\n\n\n\nprint_indic_prov(table_indic_all, \"Shanghai\")\n\nIndicateurs pour la province de: ShanghaiIndicateursEstimationMean delay onset-hosp4.0Percentage of recovery46.7Median age of the cases67.0\n\nprint_indic_prov(table_indic_all, \"Jiangsu\")\n\nIndicateurs pour la province de: JiangsuIndicateursEstimationMean delay onset-hosp6.0Percentage of recovery71.4Median age of the cases55.0", + "title": "45  Writing functions", + "section": "45.7 Using purrr: writing functions that can be iteratively applied", + "text": "45.7 Using purrr: writing functions that can be iteratively applied\n\nModify class of multiple columns in a dataset\nLet’s say many character variables in the original linelist data need to be changes to “factor” for analysis and plotting purposes. Instead of repeating the step several times, we can just use lapply() to do the transformation of all variables concerned on a single line of code.\nCAUTION: lapply() returns a list, thus its use may require an additional modification as a last step.\nThe same step can be done using map_if() function from the purrr package\n\nlinelist_factor2 <- linelist %>%\n purrr::map_if(is.character, as.factor)\n\n\nlinelist_factor2 %>%\n glimpse()\n\nList of 30\n $ case_id : Factor w/ 5888 levels \"00031d\",\"00086d\",..: 2134 3022 396 4203 3084 4347 179 1241 5594 430 ...\n $ generation : num [1:5888] 4 4 2 3 3 3 4 4 4 4 ...\n $ date_infection : Date[1:5888], format: \"2014-05-08\" NA ...\n $ date_onset : Date[1:5888], format: \"2014-05-13\" \"2014-05-13\" ...\n $ date_hospitalisation: Date[1:5888], format: \"2014-05-15\" \"2014-05-14\" ...\n $ date_outcome : Date[1:5888], format: NA \"2014-05-18\" ...\n $ outcome : Factor w/ 2 levels \"Death\",\"Recover\": NA 2 2 NA 2 2 2 1 2 1 ...\n $ gender : Factor w/ 2 levels \"f\",\"m\": 2 1 2 1 2 1 1 1 2 1 ...\n $ age : num [1:5888] 2 3 56 18 3 16 16 0 61 27 ...\n $ age_unit : Factor w/ 2 levels \"months\",\"years\": 2 2 2 2 2 2 2 2 2 2 ...\n $ age_years : num [1:5888] 2 3 56 18 3 16 16 0 61 27 ...\n $ age_cat : Factor w/ 8 levels \"0-4\",\"5-9\",\"10-14\",..: 1 1 7 4 1 4 4 1 7 5 ...\n $ age_cat5 : Factor w/ 18 levels \"0-4\",\"5-9\",\"10-14\",..: 1 1 12 4 1 4 4 1 13 6 ...\n $ hospital : Factor w/ 6 levels \"Central Hospital\",..: 4 3 6 5 2 5 3 3 3 3 ...\n $ lon : num [1:5888] -13.2 -13.2 -13.2 -13.2 -13.2 ...\n $ lat : num [1:5888] 8.47 8.45 8.46 8.48 8.46 ...\n $ infector : Factor w/ 2697 levels \"00031d\",\"002e6c\",..: 2594 NA NA 2635 180 1799 1407 195 NA NA ...\n $ source : Factor w/ 2 levels \"funeral\",\"other\": 2 NA NA 2 2 2 2 2 NA NA ...\n $ wt_kg : num [1:5888] 27 25 91 41 36 56 47 0 86 69 ...\n $ ht_cm : num [1:5888] 48 59 238 135 71 116 87 11 226 174 ...\n $ ct_blood : num [1:5888] 22 22 21 23 23 21 21 22 22 22 ...\n $ fever : Factor w/ 2 levels \"no\",\"yes\": 1 NA NA 1 1 1 NA 1 1 1 ...\n $ chills : Factor w/ 2 levels \"no\",\"yes\": 1 NA NA 1 1 1 NA 1 1 1 ...\n $ cough : Factor w/ 2 levels \"no\",\"yes\": 2 NA NA 1 2 2 NA 2 2 2 ...\n $ aches : Factor w/ 2 levels \"no\",\"yes\": 1 NA NA 1 1 1 NA 1 1 1 ...\n $ vomit : Factor w/ 2 levels \"no\",\"yes\": 2 NA NA 1 2 2 NA 2 2 1 ...\n $ temp : num [1:5888] 36.8 36.9 36.9 36.8 36.9 37.6 37.3 37 36.4 35.9 ...\n $ time_admission : Factor w/ 1072 levels \"00:10\",\"00:29\",..: NA 308 746 415 514 589 609 297 409 387 ...\n $ bmi : num [1:5888] 117.2 71.8 16.1 22.5 71.4 ...\n $ days_onset_hosp : num [1:5888] 2 1 2 2 1 1 2 1 1 2 ...\n\n\n\n\nIteratively produce graphs for different levels of a variable\nWe will produce here pie chart to look at the distribution of patient’s outcome in China during the H7N9 outbreak for each province. Instead of repeating the code for each of them, we will just apply a function that we will create.\n\n#precising options for the use of highchart\noptions(highcharter.theme = highcharter::hc_theme_smpl(tooltip = list(valueDecimals = 2)))\n\n\n#create a function called \"chart_outcome_province\" that takes as argument the dataset and the name of the province for which to plot the distribution of the outcome.\n\nchart_outcome_province <- function(data_used, prov){\n \n tab_prov <- data_used %>% \n filter(province == prov,\n !is.na(outcome))%>% \n group_by(outcome) %>% \n count() %>%\n adorn_totals(where = \"row\") %>% \n adorn_percentages(denominator = \"col\", )%>%\n mutate(\n perc_outcome= round(n*100,2))\n \n \n tab_prov %>%\n filter(outcome != \"Total\") %>% \n highcharter::hchart(\n \"pie\", hcaes(x = outcome, y = perc_outcome),\n name = paste0(\"Distibution of the outcome in:\", prov)\n )\n \n}\n\nchart_outcome_province(flu_china, \"Shanghai\")\n\n\n\n\nchart_outcome_province(flu_china,\"Zhejiang\")\n\n\n\n\nchart_outcome_province(flu_china,\"Jiangsu\")\n\n\n\n\n\n\n\nIteratively produce tables for different levels of a variable\nHere we will create three indicators to summarize in a table and we would like to produce this table for each of the provinces. Our indicators are the delay between onset and hospitalization, the percentage of recovery and the median age of cases.\n\nindic_1 <- flu_china %>% \n group_by(province) %>% \n mutate(\n date_hosp= strptime(date_of_hospitalisation, format = \"%m/%d/%Y\"),\n date_ons= strptime(date_of_onset, format = \"%m/%d/%Y\"), \n delay_onset_hosp= as.numeric(date_hosp - date_ons)/86400,\n mean_delay_onset_hosp = round(mean(delay_onset_hosp, na.rm=TRUE ), 0)) %>%\n select(province, mean_delay_onset_hosp) %>% \n distinct()\n \n\nindic_2 <- flu_china %>% \n filter(!is.na(outcome)) %>% \n group_by(province, outcome) %>% \n count() %>%\n pivot_wider(names_from = outcome, values_from = n) %>% \n adorn_totals(where = \"col\") %>% \n mutate(\n perc_recovery= round((Recover/Total)*100,2))%>% \n select(province, perc_recovery)\n \n \n \nindic_3 <- flu_china %>% \n group_by(province) %>% \n mutate(\n median_age_cases = median(as.numeric(age), na.rm = TRUE)\n ) %>% \n select(province, median_age_cases) %>% \n distinct()\n\nWarning: There was 1 warning in `mutate()`.\nℹ In argument: `median_age_cases = median(as.numeric(age), na.rm = TRUE)`.\nℹ In group 11: `province = \"Shanghai\"`.\nCaused by warning in `median()`:\n! NAs introduced by coercion\n\n#join the three indicator datasets\n\ntable_indic_all <- indic_1 %>% \n dplyr::left_join(indic_2, by = \"province\") %>% \n left_join(indic_3, by = \"province\")\n\n\n#print the indicators in a flextable\n\n\nprint_indic_prov <- function(table_used, prov){\n \n #first transform a bit the dataframe for printing ease\n indic_prov <- table_used %>%\n filter(province==prov) %>%\n pivot_longer(names_to = \"Indicateurs\", cols = 2:4) %>% \n mutate( indic_label = factor(Indicateurs,\n levels= c(\"mean_delay_onset_hosp\",\"perc_recovery\",\"median_age_cases\"),\n labels=c(\"Mean delay onset-hosp\",\"Percentage of recovery\", \"Median age of the cases\"))\n ) %>% \n ungroup(province) %>% \n select(indic_label, value)\n \n\n tab_print <- flextable(indic_prov) %>%\n theme_vanilla() %>% \n flextable::fontsize(part = \"body\", size = 10) \n \n \n tab_print <- tab_print %>% \n autofit() %>%\n set_header_labels( \n indic_label= \"Indicateurs\", value= \"Estimation\") %>%\n flextable::bg( bg = \"darkblue\", part = \"header\") %>%\n flextable::bold(part = \"header\") %>%\n flextable::color(color = \"white\", part = \"header\") %>% \n add_header_lines(values = paste0(\"Indicateurs pour la province de: \", prov)) %>% \nbold(part = \"header\")\n \n tab_print <- set_formatter_type(tab_print,\n fmt_double = \"%.2f\",\n na_str = \"-\")\n\ntab_print \n \n}\n\n\n\n\nprint_indic_prov(table_indic_all, \"Shanghai\")\n\nWarning in set_formatter_type(tab_print, fmt_double = \"%.2f\", na_str = \"-\"):\nUse `colformat_*()` instead.\n\n\nIndicateurs pour la province de: ShanghaiIndicateursEstimationMean delay onset-hosp4.0Percentage of recovery46.7Median age of the cases67.0\n\nprint_indic_prov(table_indic_all, \"Jiangsu\")\n\nWarning in set_formatter_type(tab_print, fmt_double = \"%.2f\", na_str = \"-\"):\nUse `colformat_*()` instead.\n\n\nIndicateurs pour la province de: JiangsuIndicateursEstimationMean delay onset-hosp6.0Percentage of recovery71.4Median age of the cases55.0", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#tips-and-best-practices-for-well-functioning-functions", "href": "new_pages/writing_functions.html#tips-and-best-practices-for-well-functioning-functions", - "title": "44  Writing functions", - "section": "44.8 Tips and best Practices for well functioning functions", - "text": "44.8 Tips and best Practices for well functioning functions\nFunctional programming is meant to ease code and facilitates its reading. It should produce the contrary. The tips below will help you having a clean code and easy to read code.\n\nNaming and syntax\n\nAvoid using character that could have been easily already taken by other functions already existing in your environment\nIt is recommended for the function name to be short and straightforward to understand for another reader\nIt is preferred to use verbs as the function name and nouns for the argument names.\n\n\n\nColumn names and tidy evaluation\nIf you want to know how to reference column names that are provided to your code as arguments, read this tidyverse programming guidance. Among the topics covered are tidy evaluation and use of the embrace { } “double braces”\nFor example, here is a quick skeleton template code from page tutorial mentioned just above:\n\nvar_summary <- function(data, var) {\n data %>%\n summarise(n = n(), min = min({{ var }}), max = max({{ var }}))\n}\nmtcars %>% \n group_by(cyl) %>% \n var_summary(mpg)\n\n\n\nTesting and Error handling\nThe more complicated a function’s task the higher the possibility of errors. Thus it is sometimes necessary to add some verification within the funtion to help quickly understand where the error is from and find a way t fix it.\n\nIt can be more than recommended to introduce a check on the missingness of one argument using missing(argument). This simple check can return “TRUE” or “FALSE” value.\n\n\ncontain_covid19_missing <- function(barrier_gest, wear_mask, get_vaccine){\n \n if (missing(barrier_gest)) (print(\"please provide arg1\"))\n if (missing(wear_mask)) print(\"please provide arg2\")\n if (missing(get_vaccine)) print(\"please provide arg3\")\n\n\n if (!barrier_gest == \"yes\" | wear_mask ==\"yes\" | get_vaccine == \"yes\" ) \n \n return (\"you can do better\")\n \n else(\"please make sure all are yes, this pandemic has to end!\")\n}\n\n\ncontain_covid19_missing(get_vaccine = \"yes\")\n\n[1] \"please provide arg1\"\n[1] \"please provide arg2\"\n\n\nError in contain_covid19_missing(get_vaccine = \"yes\"): argument \"barrier_gest\" is missing, with no default\n\n\n\nUse stop() for more detectable errors.\n\n\ncontain_covid19_stop <- function(barrier_gest, wear_mask, get_vaccine){\n \n if(!is.character(barrier_gest)) (stop(\"arg1 should be a character, please enter the value with `yes`, `no` or `sometimes\"))\n \n if (barrier_gest == \"yes\" & wear_mask ==\"yes\" & get_vaccine == \"yes\" ) \n \n return (\"success\")\n \n else(\"please make sure all are yes, this pandemic has to end!\")\n}\n\n\ncontain_covid19_stop(barrier_gest=1, wear_mask=\"yes\", get_vaccine = \"no\")\n\nError in contain_covid19_stop(barrier_gest = 1, wear_mask = \"yes\", get_vaccine = \"no\"): arg1 should be a character, please enter the value with `yes`, `no` or `sometimes\n\n\n\nAs we see when we run most of the built-in functions, there are messages and warnings that can pop-up in certain conditions. We can integrate those in our written functions by using the functions message() and warning().\nWe can handle errors also by using safely() which takes one function as an argument and executes it in a safe way. In fact the function will execute without stopping if it encounters an error. safely() returns as output a list with two objects which are the results and the error it “skipped”.\n\nWe can verify by first running the mean() as function, then run it with safely().\n\nmap(linelist, mean)\n\n$case_id\n[1] NA\n\n$generation\n[1] 16.56165\n\n$date_infection\n[1] NA\n\n$date_onset\n[1] NA\n\n$date_hospitalisation\n[1] \"2014-11-03\"\n\n$date_outcome\n[1] NA\n\n$outcome\n[1] NA\n\n$gender\n[1] NA\n\n$age\n[1] NA\n\n$age_unit\n[1] NA\n\n$age_years\n[1] NA\n\n$age_cat\n[1] NA\n\n$age_cat5\n[1] NA\n\n$hospital\n[1] NA\n\n$lon\n[1] -13.23381\n\n$lat\n[1] 8.469638\n\n$infector\n[1] NA\n\n$source\n[1] NA\n\n$wt_kg\n[1] 52.64487\n\n$ht_cm\n[1] 124.9633\n\n$ct_blood\n[1] 21.20686\n\n$fever\n[1] NA\n\n$chills\n[1] NA\n\n$cough\n[1] NA\n\n$aches\n[1] NA\n\n$vomit\n[1] NA\n\n$temp\n[1] NA\n\n$time_admission\n[1] NA\n\n$bmi\n[1] 46.89023\n\n$days_onset_hosp\n[1] NA\n\n\n\nsafe_mean <- safely(mean)\nlinelist %>% \n map(safe_mean)\n\n$case_id\n$case_id$result\n[1] NA\n\n$case_id$error\nNULL\n\n\n$generation\n$generation$result\n[1] 16.56165\n\n$generation$error\nNULL\n\n\n$date_infection\n$date_infection$result\n[1] NA\n\n$date_infection$error\nNULL\n\n\n$date_onset\n$date_onset$result\n[1] NA\n\n$date_onset$error\nNULL\n\n\n$date_hospitalisation\n$date_hospitalisation$result\n[1] \"2014-11-03\"\n\n$date_hospitalisation$error\nNULL\n\n\n$date_outcome\n$date_outcome$result\n[1] NA\n\n$date_outcome$error\nNULL\n\n\n$outcome\n$outcome$result\n[1] NA\n\n$outcome$error\nNULL\n\n\n$gender\n$gender$result\n[1] NA\n\n$gender$error\nNULL\n\n\n$age\n$age$result\n[1] NA\n\n$age$error\nNULL\n\n\n$age_unit\n$age_unit$result\n[1] NA\n\n$age_unit$error\nNULL\n\n\n$age_years\n$age_years$result\n[1] NA\n\n$age_years$error\nNULL\n\n\n$age_cat\n$age_cat$result\n[1] NA\n\n$age_cat$error\nNULL\n\n\n$age_cat5\n$age_cat5$result\n[1] NA\n\n$age_cat5$error\nNULL\n\n\n$hospital\n$hospital$result\n[1] NA\n\n$hospital$error\nNULL\n\n\n$lon\n$lon$result\n[1] -13.23381\n\n$lon$error\nNULL\n\n\n$lat\n$lat$result\n[1] 8.469638\n\n$lat$error\nNULL\n\n\n$infector\n$infector$result\n[1] NA\n\n$infector$error\nNULL\n\n\n$source\n$source$result\n[1] NA\n\n$source$error\nNULL\n\n\n$wt_kg\n$wt_kg$result\n[1] 52.64487\n\n$wt_kg$error\nNULL\n\n\n$ht_cm\n$ht_cm$result\n[1] 124.9633\n\n$ht_cm$error\nNULL\n\n\n$ct_blood\n$ct_blood$result\n[1] 21.20686\n\n$ct_blood$error\nNULL\n\n\n$fever\n$fever$result\n[1] NA\n\n$fever$error\nNULL\n\n\n$chills\n$chills$result\n[1] NA\n\n$chills$error\nNULL\n\n\n$cough\n$cough$result\n[1] NA\n\n$cough$error\nNULL\n\n\n$aches\n$aches$result\n[1] NA\n\n$aches$error\nNULL\n\n\n$vomit\n$vomit$result\n[1] NA\n\n$vomit$error\nNULL\n\n\n$temp\n$temp$result\n[1] NA\n\n$temp$error\nNULL\n\n\n$time_admission\n$time_admission$result\n[1] NA\n\n$time_admission$error\nNULL\n\n\n$bmi\n$bmi$result\n[1] 46.89023\n\n$bmi$error\nNULL\n\n\n$days_onset_hosp\n$days_onset_hosp$result\n[1] NA\n\n$days_onset_hosp$error\nNULL\n\n\nAs said previously, well commenting our codes is already a good way for having documentation in our work.", + "title": "45  Writing functions", + "section": "45.8 Tips and best Practices for well functioning functions", + "text": "45.8 Tips and best Practices for well functioning functions\nFunctional programming is meant to ease code and facilitates its reading. It should produce the contrary. The tips below will help you having a clean code and easy to read code.\n\nNaming and syntax\n\nAvoid using character that could have been easily already taken by other functions already existing in your environment.\nIt is recommended for the function name to be short and straightforward to understand for another reader.\nIt is preferred to use verbs as the function name and nouns for the argument names.\n\n\n\nColumn names and tidy evaluation\nIf you want to know how to reference column names that are provided to your code as arguments, read this tidyverse programming guidance. Among the topics covered are tidy evaluation and use of the embrace { } “double braces”\nFor example, here is a quick skeleton template code from page tutorial mentioned just above:\n\nvar_summary <- function(data, var) {\n data %>%\n summarise(n = n(), min = min({{ var }}), max = max({{ var }}))\n}\nmtcars %>% \n group_by(cyl) %>% \n var_summary(mpg)\n\n\n\nTesting and Error handling\nThe more complicated a function’s task the higher the possibility of errors. Thus it is sometimes necessary to add some verification within the funtion to help quickly understand where the error is from and find a way t fix it.\n\nIt can be more than recommended to introduce a check on the missingness of one argument using missing(argument). This simple check can return “TRUE” or “FALSE” value.\n\n\ncontain_covid19_missing <- function(barrier_gest, wear_mask, get_vaccine){\n \n if (missing(barrier_gest)) (print(\"please provide arg1\"))\n if (missing(wear_mask)) print(\"please provide arg2\")\n if (missing(get_vaccine)) print(\"please provide arg3\")\n\n\n if (!barrier_gest == \"yes\" | wear_mask ==\"yes\" | get_vaccine == \"yes\" ) \n \n return (\"you can do better\")\n \n else(\"please make sure all are yes, this pandemic has to end!\")\n}\n\n\ncontain_covid19_missing(get_vaccine = \"yes\")\n\n[1] \"please provide arg1\"\n[1] \"please provide arg2\"\n\n\nError in contain_covid19_missing(get_vaccine = \"yes\"): argument \"barrier_gest\" is missing, with no default\n\n\n\nUse stop() for more detectable errors.\n\n\ncontain_covid19_stop <- function(barrier_gest, wear_mask, get_vaccine){\n \n if(!is.character(barrier_gest)) (stop(\"arg1 should be a character, please enter the value with `yes`, `no` or `sometimes\"))\n \n if (barrier_gest == \"yes\" & wear_mask ==\"yes\" & get_vaccine == \"yes\" ) \n \n return (\"success\")\n \n else(\"please make sure all are yes, this pandemic has to end!\")\n}\n\n\ncontain_covid19_stop(barrier_gest=1, wear_mask=\"yes\", get_vaccine = \"no\")\n\nError in contain_covid19_stop(barrier_gest = 1, wear_mask = \"yes\", get_vaccine = \"no\"): arg1 should be a character, please enter the value with `yes`, `no` or `sometimes\n\n\n\nAs we see when we run most of the built-in functions, there are messages and warnings that can pop-up in certain conditions. We can integrate those in our written functions by using the functions message() and warning().\nWe can handle errors also by using safely() which takes one function as an argument and executes it in a safe way. In fact the function will execute without stopping if it encounters an error. safely() returns as output a list with two objects which are the results and the error it “skipped”.\n\nWe can verify by first running the mean() as function, then run it with safely().\n\nmap(linelist, mean)\n\n$case_id\n[1] NA\n\n$generation\n[1] 16.56165\n\n$date_infection\n[1] NA\n\n$date_onset\n[1] NA\n\n$date_hospitalisation\n[1] \"2014-11-03\"\n\n$date_outcome\n[1] NA\n\n$outcome\n[1] NA\n\n$gender\n[1] NA\n\n$age\n[1] NA\n\n$age_unit\n[1] NA\n\n$age_years\n[1] NA\n\n$age_cat\n[1] NA\n\n$age_cat5\n[1] NA\n\n$hospital\n[1] NA\n\n$lon\n[1] -13.23381\n\n$lat\n[1] 8.469638\n\n$infector\n[1] NA\n\n$source\n[1] NA\n\n$wt_kg\n[1] 52.64487\n\n$ht_cm\n[1] 124.9633\n\n$ct_blood\n[1] 21.20686\n\n$fever\n[1] NA\n\n$chills\n[1] NA\n\n$cough\n[1] NA\n\n$aches\n[1] NA\n\n$vomit\n[1] NA\n\n$temp\n[1] NA\n\n$time_admission\n[1] NA\n\n$bmi\n[1] 46.89023\n\n$days_onset_hosp\n[1] NA\n\n\n\nsafe_mean <- safely(mean)\nlinelist %>% \n map(safe_mean)\n\n$case_id\n$case_id$result\n[1] NA\n\n$case_id$error\nNULL\n\n\n$generation\n$generation$result\n[1] 16.56165\n\n$generation$error\nNULL\n\n\n$date_infection\n$date_infection$result\n[1] NA\n\n$date_infection$error\nNULL\n\n\n$date_onset\n$date_onset$result\n[1] NA\n\n$date_onset$error\nNULL\n\n\n$date_hospitalisation\n$date_hospitalisation$result\n[1] \"2014-11-03\"\n\n$date_hospitalisation$error\nNULL\n\n\n$date_outcome\n$date_outcome$result\n[1] NA\n\n$date_outcome$error\nNULL\n\n\n$outcome\n$outcome$result\n[1] NA\n\n$outcome$error\nNULL\n\n\n$gender\n$gender$result\n[1] NA\n\n$gender$error\nNULL\n\n\n$age\n$age$result\n[1] NA\n\n$age$error\nNULL\n\n\n$age_unit\n$age_unit$result\n[1] NA\n\n$age_unit$error\nNULL\n\n\n$age_years\n$age_years$result\n[1] NA\n\n$age_years$error\nNULL\n\n\n$age_cat\n$age_cat$result\n[1] NA\n\n$age_cat$error\nNULL\n\n\n$age_cat5\n$age_cat5$result\n[1] NA\n\n$age_cat5$error\nNULL\n\n\n$hospital\n$hospital$result\n[1] NA\n\n$hospital$error\nNULL\n\n\n$lon\n$lon$result\n[1] -13.23381\n\n$lon$error\nNULL\n\n\n$lat\n$lat$result\n[1] 8.469638\n\n$lat$error\nNULL\n\n\n$infector\n$infector$result\n[1] NA\n\n$infector$error\nNULL\n\n\n$source\n$source$result\n[1] NA\n\n$source$error\nNULL\n\n\n$wt_kg\n$wt_kg$result\n[1] 52.64487\n\n$wt_kg$error\nNULL\n\n\n$ht_cm\n$ht_cm$result\n[1] 124.9633\n\n$ht_cm$error\nNULL\n\n\n$ct_blood\n$ct_blood$result\n[1] 21.20686\n\n$ct_blood$error\nNULL\n\n\n$fever\n$fever$result\n[1] NA\n\n$fever$error\nNULL\n\n\n$chills\n$chills$result\n[1] NA\n\n$chills$error\nNULL\n\n\n$cough\n$cough$result\n[1] NA\n\n$cough$error\nNULL\n\n\n$aches\n$aches$result\n[1] NA\n\n$aches$error\nNULL\n\n\n$vomit\n$vomit$result\n[1] NA\n\n$vomit$error\nNULL\n\n\n$temp\n$temp$result\n[1] NA\n\n$temp$error\nNULL\n\n\n$time_admission\n$time_admission$result\n[1] NA\n\n$time_admission$error\nNULL\n\n\n$bmi\n$bmi$result\n[1] 46.89023\n\n$bmi$error\nNULL\n\n\n$days_onset_hosp\n$days_onset_hosp$result\n[1] NA\n\n$days_onset_hosp$error\nNULL\n\n\nAs said previously, well commenting our codes is already a good way for having documentation in our work.", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/writing_functions.html#resources", "href": "new_pages/writing_functions.html#resources", - "title": "44  Writing functions", - "section": "44.9 Resources", - "text": "44.9 Resources\nR for Data Science link\nCheatsheet advance R programming\nCheatsheet purr Package\nVideo-ACM talk by Hadley Wickham: The joy of functional programming (how does map_dbl work)", + "title": "45  Writing functions", + "section": "45.9 Resources", + "text": "45.9 Resources\nR for Data Science link\nCheatsheet advance R programming\nCheatsheet purr Package\nVideo-ACM talk by Hadley Wickham: The joy of functional programming (how does map_dbl work)", "crumbs": [ "Miscellaneous", - "44  Writing functions" + "45  Writing functions" ] }, { "objectID": "new_pages/directories.html", "href": "new_pages/directories.html", - "title": "45  Directory interactions", + "title": "46  Directory interactions", "section": "", - "text": "45.1 Preparation", + "text": "46.1 Preparation", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#preparation", "href": "new_pages/directories.html#preparation", - "title": "45  Directory interactions", + "title": "46  Directory interactions", "section": "", - "text": "fs package\nThe fs package is a tidyverse package that facilitate directory interactions, improving on some of the base R functions. In the sections below we will often use functions from fs.\n\npacman::p_load(\n fs, # file/directory interactions\n rio, # import/export\n here, # relative file pathways\n tidyverse) # data management and visualization\n\n\n\nPrint directory as a dendrogram tree\nUse the function dir_tree() from fs.\nProvide the folder filepath to path = and decide whether you want to show only one level (recurse = FALSE) or all files in all sub-levels (recurse = TRUE). Below we use here() as shorthand for the R project and specify its sub-folder “data”, which contains all the data used for this R handbook. We set it to display all files within “data” and its sub-folders (e.g. “cache”, “epidemic models”, “population”, “shp”, and “weather”).\n\nfs::dir_tree(path = here(\"data\"), recurse = TRUE)\n\nC:/Users/ngulu864/AppData/Local/Temp/RtmpyIElas/file7c74679b5e85/data\n├── africa_countries.geo.json\n├── cache\n│ └── epidemic_models\n│ ├── 2015-04-30\n│ │ ├── estimated_reported_cases_samples.rds\n│ │ ├── estimate_samples.rds\n│ │ ├── latest_date.rds\n│ │ ├── reported_cases.rds\n│ │ ├── summarised_estimated_reported_cases.rds\n│ │ ├── summarised_estimates.rds\n│ │ └── summary.rds\n│ ├── epinow_res.rds\n│ ├── epinow_res_small.rds\n│ ├── generation_time.rds\n│ └── incubation_period.rds\n├── case_linelists\n│ ├── cleaning_dict.csv\n│ ├── fluH7N9_China_2013.csv\n│ ├── linelist_cleaned.rds\n│ ├── linelist_cleaned.xlsx\n│ └── linelist_raw.xlsx\n├── country_demographics.csv\n├── covid_example_data\n│ ├── covid_example_data.xlsx\n│ └── covid_shapefile\n│ ├── FultonCountyZipCodes.cpg\n│ ├── FultonCountyZipCodes.dbf\n│ ├── FultonCountyZipCodes.prj\n│ ├── FultonCountyZipCodes.sbn\n│ ├── FultonCountyZipCodes.sbx\n│ ├── FultonCountyZipCodes.shp\n│ ├── FultonCountyZipCodes.shp.xml\n│ └── FultonCountyZipCodes.shx\n├── covid_incidence.csv\n├── covid_incidence_map.R\n├── district_count_data.xlsx\n├── example\n│ ├── Central Hospital.csv\n│ ├── district_weekly_count_data.xlsx\n│ ├── fluH7N9_China_2013.csv\n│ ├── hospital_linelists.xlsx\n│ ├── linelists\n│ │ ├── 20201007linelist.csv\n│ │ ├── case_linelist20201006.csv\n│ │ ├── case_linelist_2020-10-02.csv\n│ │ ├── case_linelist_2020-10-03.csv\n│ │ ├── case_linelist_2020-10-04.csv\n│ │ ├── case_linelist_2020-10-05.csv\n│ │ └── case_linelist_2020-10-08.xlsx\n│ ├── Military Hospital.csv\n│ ├── Missing.csv\n│ ├── Other.csv\n│ ├── Port Hospital.csv\n│ └── St. Mark's Maternity Hospital (SMMH).csv\n├── facility_count_data.rds\n├── flexdashboard\n│ ├── outbreak_dashboard.html\n│ ├── outbreak_dashboard.Rmd\n│ ├── outbreak_dashboard_shiny.Rmd\n│ ├── outbreak_dashboard_test.html\n│ └── outbreak_dashboard_test.Rmd\n├── fluH7N9_China_2013.csv\n├── gis\n│ ├── africa_countries.geo.json\n│ ├── covid_incidence.csv\n│ ├── covid_incidence_map.R\n│ ├── linelist_cleaned_with_adm3.rds\n│ ├── population\n│ │ ├── sle_admpop_adm3_2020.csv\n│ │ └── sle_population_statistics_sierraleone_2020.xlsx\n│ └── shp\n│ ├── README.txt\n│ ├── sle_adm3.CPG\n│ ├── sle_adm3.dbf\n│ ├── sle_adm3.prj\n│ ├── sle_adm3.sbn\n│ ├── sle_adm3.sbx\n│ ├── sle_adm3.shp\n│ ├── sle_adm3.shp.xml\n│ ├── sle_adm3.shx\n│ ├── sle_hf.CPG\n│ ├── sle_hf.dbf\n│ ├── sle_hf.prj\n│ ├── sle_hf.sbn\n│ ├── sle_hf.sbx\n│ ├── sle_hf.shp\n│ └── sle_hf.shx\n├── godata\n│ ├── cases_clean.rds\n│ ├── contacts_clean.rds\n│ ├── followups_clean.rds\n│ └── relationships_clean.rds\n├── likert_data.csv\n├── linelist_cleaned.rds\n├── linelist_cleaned.xlsx\n├── linelist_raw.xlsx\n├── make_evd_dataset-DESKTOP-JIEUMMI.R\n├── make_evd_dataset.R\n├── malaria_app\n│ ├── app.R\n│ ├── data\n│ │ └── facility_count_data.rds\n│ ├── funcs\n│ │ └── plot_epicurve.R\n│ ├── global.R\n│ ├── malaria_app.Rproj\n│ ├── server.R\n│ └── ui.R\n├── malaria_facility_count_data.rds\n├── phylo\n│ ├── sample_data_Shigella_tree.csv\n│ ├── Shigella_subtree_2.nwk\n│ ├── Shigella_subtree_2.txt\n│ └── Shigella_tree.txt\n├── rmarkdown\n│ ├── outbreak_report.docx\n│ ├── outbreak_report.html\n│ ├── outbreak_report.pdf\n│ ├── outbreak_report.pptx\n│ ├── outbreak_report.Rmd\n│ ├── report_tabbed_example.html\n│ └── report_tabbed_example.Rmd\n├── standardization\n│ ├── country_demographics.csv\n│ ├── country_demographics_2.csv\n│ ├── deaths_countryA.csv\n│ ├── deaths_countryB.csv\n│ └── world_standard_population_by_sex.csv\n├── surveys\n│ ├── population.xlsx\n│ ├── survey_data.xlsx\n│ └── survey_dict.xlsx\n└── time_series\n ├── campylobacter_germany.xlsx\n └── weather\n ├── germany_weather2002.nc\n ├── germany_weather2003.nc\n ├── germany_weather2004.nc\n ├── germany_weather2005.nc\n ├── germany_weather2006.nc\n ├── germany_weather2007.nc\n ├── germany_weather2008.nc\n ├── germany_weather2009.nc\n ├── germany_weather2010.nc\n └── germany_weather2011.nc", + "text": "fs package\nThe fs package is a tidyverse package that facilitate directory interactions, improving on some of the base R functions. In the sections below we will often use functions from fs.\n\npacman::p_load(\n fs, # file/directory interactions\n rio, # import/export\n here, # relative file pathways\n tidyverse # data management and visualization\n ) \n\n\n\nPrint directory as a dendrogram tree\nUse the function dir_tree() from fs.\nProvide the folder filepath to path = and decide whether you want to show only one level (recurse = FALSE) or all files in all sub-levels (recurse = TRUE). Below we use here() as shorthand for the R project and specify its sub-folder “data”, which contains all the data used for this R handbook. We set it to display all files within “data” and its sub-folders (e.g. “cache”, “epidemic models”, “population”, “shp”, and “weather”).\n\nfs::dir_tree(path = here(\"data\"), recurse = TRUE)\n\nC:/Users/ah1114/Documents/appliedepi/epiRhandbook_eng/data\n├── africa_countries.geo.json\n├── cache\n│ └── epidemic_models\n│ ├── 2015-04-30\n│ │ ├── estimated_reported_cases_samples.rds\n│ │ ├── estimate_samples.rds\n│ │ ├── latest_date.rds\n│ │ ├── reported_cases.rds\n│ │ ├── summarised_estimated_reported_cases.rds\n│ │ ├── summarised_estimates.rds\n│ │ └── summary.rds\n│ ├── epinow_res.rds\n│ ├── epinow_res_small.rds\n│ ├── generation_time.rds\n│ └── incubation_period.rds\n├── case_linelists\n│ ├── cleaning_dict.csv\n│ ├── fluH7N9_China_2013.csv\n│ ├── linelist_cleaned.rds\n│ ├── linelist_cleaned.xlsx\n│ └── linelist_raw.xlsx\n├── contact_tracing.rds\n├── country_demographics.csv\n├── covid_example_data\n│ ├── covid_example_data.xlsx\n│ └── covid_shapefile\n│ ├── FultonCountyZipCodes.cpg\n│ ├── FultonCountyZipCodes.dbf\n│ ├── FultonCountyZipCodes.prj\n│ ├── FultonCountyZipCodes.sbn\n│ ├── FultonCountyZipCodes.sbx\n│ ├── FultonCountyZipCodes.shp\n│ ├── FultonCountyZipCodes.shp.xml\n│ └── FultonCountyZipCodes.shx\n├── covid_incidence.csv\n├── covid_incidence_map.R\n├── district_count_data.xlsx\n├── example\n│ ├── Central Hospital.csv\n│ ├── district_weekly_count_data.xlsx\n│ ├── fluH7N9_China_2013.csv\n│ ├── hospital_linelists.xlsx\n│ ├── linelists\n│ │ ├── 20201007linelist.csv\n│ │ ├── case_linelist20201006.csv\n│ │ ├── case_linelist_2020-10-02.csv\n│ │ ├── case_linelist_2020-10-03.csv\n│ │ ├── case_linelist_2020-10-04.csv\n│ │ ├── case_linelist_2020-10-05.csv\n│ │ └── case_linelist_2020-10-08.xlsx\n│ ├── Military Hospital.csv\n│ ├── Missing.csv\n│ ├── Other.csv\n│ ├── Port Hospital.csv\n│ └── St. Mark's Maternity Hospital (SMMH).csv\n├── facility_count_data.rds\n├── flexdashboard\n│ ├── outbreak_dashboard.html\n│ ├── outbreak_dashboard.Rmd\n│ ├── outbreak_dashboard_shiny.Rmd\n│ ├── outbreak_dashboard_test.html\n│ └── outbreak_dashboard_test.Rmd\n├── fluH7N9_China_2013.csv\n├── gis\n│ ├── africa_countries.geo.json\n│ ├── covid_incidence.csv\n│ ├── covid_incidence_map.R\n│ ├── linelist_cleaned_with_adm3.rds\n│ ├── population\n│ │ ├── sle_admpop_adm3_2020.csv\n│ │ └── sle_population_statistics_sierraleone_2020.xlsx\n│ └── shp\n│ ├── README.txt\n│ ├── sle_adm3.CPG\n│ ├── sle_adm3.dbf\n│ ├── sle_adm3.prj\n│ ├── sle_adm3.sbn\n│ ├── sle_adm3.sbx\n│ ├── sle_adm3.shp\n│ ├── sle_adm3.shp.xml\n│ ├── sle_adm3.shx\n│ ├── sle_hf.CPG\n│ ├── sle_hf.dbf\n│ ├── sle_hf.prj\n│ ├── sle_hf.sbn\n│ ├── sle_hf.sbx\n│ ├── sle_hf.shp\n│ └── sle_hf.shx\n├── godata\n│ ├── cases_clean.rds\n│ ├── contacts_clean.rds\n│ ├── followups_clean.rds\n│ └── relationships_clean.rds\n├── likert_data.csv\n├── linelist_cleaned.rds\n├── linelist_cleaned.xlsx\n├── linelist_raw.xlsx\n├── make_evd_dataset-DESKTOP-JIEUMMI.R\n├── make_evd_dataset.R\n├── malaria_app\n│ ├── app.R\n│ ├── data\n│ │ └── facility_count_data.rds\n│ ├── funcs\n│ │ └── plot_epicurve.R\n│ ├── global.R\n│ ├── malaria_app.Rproj\n│ ├── server.R\n│ └── ui.R\n├── malaria_facility_count_data.rds\n├── phylo\n│ ├── sample_data_Shigella_tree.csv\n│ ├── Shigella_subtree_2.nwk\n│ ├── Shigella_subtree_2.txt\n│ └── Shigella_tree.txt\n├── rmarkdown\n│ ├── outbreak_report.docx\n│ ├── outbreak_report.html\n│ ├── outbreak_report.pdf\n│ ├── outbreak_report.pptx\n│ ├── outbreak_report.Rmd\n│ ├── report_tabbed_example.html\n│ └── report_tabbed_example.Rmd\n├── standardization\n│ ├── country_demographics.csv\n│ ├── country_demographics_2.csv\n│ ├── deaths_countryA.csv\n│ ├── deaths_countryB.csv\n│ └── world_standard_population_by_sex.csv\n├── surveys\n│ ├── population.xlsx\n│ ├── survey_data.xlsx\n│ ├── survey_dict.xlsx\n│ └── tab_survey_output.rds\n└── time_series\n ├── campylobacter_germany.xlsx\n └── weather\n ├── germany_weather2002.nc\n ├── germany_weather2003.nc\n ├── germany_weather2004.nc\n ├── germany_weather2005.nc\n ├── germany_weather2006.nc\n ├── germany_weather2007.nc\n ├── germany_weather2008.nc\n ├── germany_weather2009.nc\n ├── germany_weather2010.nc\n └── germany_weather2011.nc", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#list-files-in-a-directory", "href": "new_pages/directories.html#list-files-in-a-directory", - "title": "45  Directory interactions", - "section": "45.2 List files in a directory", - "text": "45.2 List files in a directory\nTo list just the file names in a directory you can use dir() from base R. For example, this command lists the file names of the files in the “population” subfolder of the “data” folder in an R project. The relative filepath is provided using here() (which you can read about more in the Import and export page).\n\n# file names\ndir(here(\"data\", \"gis\", \"population\"))\n\n[1] \"sle_admpop_adm3_2020.csv\" \n[2] \"sle_population_statistics_sierraleone_2020.xlsx\"\n\n\nTo list the full file paths of the directory’s files, you can use you can use dir_ls() from fs. A base R alternative is list.files().\n\n# file paths\ndir_ls(here(\"data\", \"gis\", \"population\"))\n\nC:/Users/ngulu864/AppData/Local/Temp/RtmpyIElas/file7c74679b5e85/data/gis/population/sle_admpop_adm3_2020.csv\nC:/Users/ngulu864/AppData/Local/Temp/RtmpyIElas/file7c74679b5e85/data/gis/population/sle_population_statistics_sierraleone_2020.xlsx\n\n\nTo get all the metadata information about each file in a directory, (e.g. path, modification date, etc.) you can use dir_info() from fs.\nThis can be particularly useful if you want to extract the last modification time of the file, for example if you want to import the most recent version of a file. For an example of this, see the Import and export page.\n\n# file info\ndir_info(here(\"data\", \"gis\", \"population\"))\n\nHere is the data frame returned. Scroll to the right to see all the columns.", + "title": "46  Directory interactions", + "section": "46.2 List files in a directory", + "text": "46.2 List files in a directory\nTo list just the file names in a directory you can use dir() from base R. For example, this command lists the file names of the files in the “population” subfolder of the “data” folder in an R project. The relative filepath is provided using here() (which you can read about more in the Import and export page).\n\n# file names\ndir(here(\"data\", \"gis\", \"population\"))\n\n[1] \"sle_admpop_adm3_2020.csv\" \n[2] \"sle_population_statistics_sierraleone_2020.xlsx\"\n\n\nTo list the full file paths of the directory’s files, you can use you can use dir_ls() from fs. A base R alternative is list.files().\n\n# file paths\ndir_ls(here(\"data\", \"gis\", \"population\"))\n\nC:/Users/ah1114/Documents/appliedepi/epiRhandbook_eng/data/gis/population/sle_admpop_adm3_2020.csv\nC:/Users/ah1114/Documents/appliedepi/epiRhandbook_eng/data/gis/population/sle_population_statistics_sierraleone_2020.xlsx\n\n\nTo get all the metadata information about each file in a directory, (e.g. path, modification date, etc.) you can use dir_info() from fs.\nThis can be particularly useful if you want to extract the last modification time of the file, for example if you want to import the most recent version of a file. For an example of this, see the Import and export page.\n\n# file info\ndir_info(here(\"data\", \"gis\", \"population\"))\n\nHere is the data frame returned. Scroll to the right to see all the columns.", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#file-information", "href": "new_pages/directories.html#file-information", - "title": "45  Directory interactions", - "section": "45.3 File information", - "text": "45.3 File information\nTo extract metadata information about a specific file, you can use file_info() from fs (or file.info() from base R).\n\nfile_info(here(\"data\", \"case_linelists\", \"linelist_cleaned.rds\"))\n\n\n\n\n\n\n\nHere we use the $ to index the result and return only the modification_time value.\n\nfile_info(here(\"data\", \"case_linelists\", \"linelist_cleaned.rds\"))$modification_time\n\n[1] \"2024-02-18 14:56:16 CET\"", + "title": "46  Directory interactions", + "section": "46.3 File information", + "text": "46.3 File information\nTo extract metadata information about a specific file, you can use file_info() from fs (or file.info() from base R).\n\nfile_info(here(\"data\", \"case_linelists\", \"linelist_cleaned.rds\"))\n\n\n\n\n\n\n\nHere we use the $ to index the result and return only the modification_time value.\n\nfile_info(here(\"data\", \"case_linelists\", \"linelist_cleaned.rds\"))$modification_time\n\n[1] \"2024-07-24 13:05:01 PDT\"", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#check-if-exists", "href": "new_pages/directories.html#check-if-exists", - "title": "45  Directory interactions", - "section": "45.4 Check if exists", - "text": "45.4 Check if exists\n\nR objects\nYou can use exists() from base R to check whether an R object exists within R (supply the object name in quotes).\n\nexists(\"linelist\")\n\n[1] FALSE\n\n\nNote that some base R packages use generic object names like “data” behind the scenes, that will appear as TRUE unless inherit = FALSE is specified. This is one reason to not name your dataset “data”.\n\nexists(\"data\")\n\n[1] TRUE\n\nexists(\"data\", inherit = FALSE)\n\n[1] FALSE\n\n\nIf you are writing a function, you should use missing() from base R to check if an argument is present or not, instead of exists().\n\n\nDirectories\nTo check whether a directory exists, provide the file path (and file name) to is_dir() from fs. Scroll to the right to see that TRUE is printed.\n\nis_dir(here(\"data\"))\n\nC:/Users/ngulu864/AppData/Local/Temp/RtmpyIElas/file7c74679b5e85/data \n TRUE \n\n\nAn alternative is file.exists() from base R.\n\n\nFiles\nTo check if a specific file exists, use is_file() from fs. Scroll to the right to see that TRUE is printed.\n\nis_file(here(\"data\", \"case_linelists\", \"linelist_cleaned.rds\"))\n\nC:/Users/ngulu864/AppData/Local/Temp/RtmpyIElas/file7c74679b5e85/data/case_linelists/linelist_cleaned.rds \n TRUE \n\n\nA base R alternative is file.exists().", + "title": "46  Directory interactions", + "section": "46.4 Check if exists", + "text": "46.4 Check if exists\n\nR objects\nYou can use exists() from base R to check whether an R object exists within R (supply the object name in quotes).\n\nexists(\"linelist\")\n\n[1] FALSE\n\n\nNote that some base R packages use generic object names like “data” behind the scenes, that will appear as TRUE unless inherit = FALSE is specified. This is one reason to not name your dataset “data”.\n\nexists(\"data\")\n\n[1] TRUE\n\nexists(\"data\", inherit = FALSE)\n\n[1] FALSE\n\n\nIf you are writing a function, you should use missing() from base R to check if an argument is present or not, instead of exists().\n\n\nDirectories\nTo check whether a directory exists, provide the file path (and file name) to is_dir() from fs. Scroll to the right to see that TRUE is printed.\n\nis_dir(here(\"data\"))\n\nC:/Users/ah1114/Documents/appliedepi/epiRhandbook_eng/data \n TRUE \n\n\nAn alternative is file.exists() from base R.\n\n\nFiles\nTo check if a specific file exists, use is_file() from fs. Scroll to the right to see that TRUE is printed.\n\nis_file(here(\"data\", \"case_linelists\", \"linelist_cleaned.rds\"))\n\nC:/Users/ah1114/Documents/appliedepi/epiRhandbook_eng/data/case_linelists/linelist_cleaned.rds \n TRUE \n\n\nA base R alternative is file.exists().", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#create", "href": "new_pages/directories.html#create", - "title": "45  Directory interactions", - "section": "45.5 Create", - "text": "45.5 Create\n\nDirectories\nTo create a new directory (folder) you can use dir_create() from fs. If the directory already exists, it will not be overwritten and no error will be returned.\n\ndir_create(here(\"data\", \"test\"))\n\nAn alternative is dir.create() from base R, which will show an error if the directory already exists. In contrast, dir_create() in this scenario will be silent.\n\n\nFiles\nYou can create an (empty) file with file_create() from fs. If the file already exists, it will not be over-written or changed.\n\nfile_create(here(\"data\", \"test.rds\"))\n\nA base R alternative is file.create(). But if the file already exists, this option will truncate it. If you use file_create() the file will be left unchanged.\n\n\nCreate if does not exists\nUNDER CONSTRUCTION", + "title": "46  Directory interactions", + "section": "46.5 Create", + "text": "46.5 Create\n\nDirectories\nTo create a new directory (folder) you can use dir_create() from fs. If the directory already exists, it will not be overwritten and no error will be returned.\n\ndir_create(here(\"data\", \"test\"))\n\nAn alternative is dir.create() from base R, which will show an error if the directory already exists. In contrast, dir_create() in this scenario will be silent.\n\n\nFiles\nYou can create an (empty) file with file_create() from fs. If the file already exists, it will not be over-written or changed.\n\nfile_create(here(\"data\", \"test.rds\"))\n\nA base R alternative is file.create(). But if the file already exists, this option will truncate it. If you use file_create() the file will be left unchanged.", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#delete", "href": "new_pages/directories.html#delete", - "title": "45  Directory interactions", - "section": "45.6 Delete", - "text": "45.6 Delete\n\nR objects\nUse rm() from base R to remove an R object.\n\n\nDirectories\nUse dir_delete() from fs.\n\n\nFiles\nYou can delete files with file_delete() from fs.", + "title": "46  Directory interactions", + "section": "46.6 Delete", + "text": "46.6 Delete\n\nR objects\nUse rm() from base R to remove an R object.\n\n\nDirectories\nUse dir_delete() from fs.\n\n\nFiles\nYou can delete files with file_delete() from fs.", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#running-other-files", "href": "new_pages/directories.html#running-other-files", - "title": "45  Directory interactions", - "section": "45.7 Running other files", - "text": "45.7 Running other files\n\nsource()\nTo run one R script from another R script, you can use the source() command (from base R).\n\nsource(here(\"scripts\", \"cleaning_scripts\", \"clean_testing_data.R\"))\n\nThis is equivalent to viewing the above R script and clicking the “Source” button in the upper-right of the script. This will execute the script but will do it silently (no output to the R console) unless specifically intended. See the page on [Interactive console] for examples of using source() to interact with a user via the R console in question-and-answer mode.\n\n\n\n\n\n\n\n\n\n\n\nrender()\nrender() is a variation on source() most often used for R markdown scripts. You provide the input = which is the R markdown file, and also the output_format = (typically either “html_document”, “pdf_document”, “word_document”, ““)\nSee the page on Reports with R Markdown for more details. Also see the documentation for render() here or by entering ?render.\n\n\nRun files in a directory\nYou can create a for loop and use it to source() every file in a directory, as identified with dir().\n\nfor(script in dir(here(\"scripts\"), pattern = \".R$\")) { # for each script name in the R Project's \"scripts\" folder (with .R extension)\n source(here(\"scripts\", script)) # source the file with the matching name that exists in the scripts folder\n}\n\nIf you only want to run certain scripts, you can identify them by name like this:\n\nscripts_to_run <- c(\n \"epicurves.R\",\n \"demographic_tables.R\",\n \"survival_curves.R\"\n)\n\nfor(script in scripts_to_run) {\n source(here(\"scripts\", script))\n}\n\nHere is a comparison of the fs and base R functions.\n\n\nImport files in a directory\nSee the page on [Import and export] for importing and exporting individual files.\nAlso see the Import and export page for methods to automatically import the most recent file, based on a date in the file name or by looking at the file meta-data.\nSee the page on [Iteration, loops, and lists] for an example with the package purrr demonstrating:\n\nSplitting a data frame and saving it out as multiple CSV files\n\nSplitting a data frame and saving each part as a separate sheet within one Excel workbook\n\nImporting multiple CSV files and combining them into one dataframe\n\nImporting an Excel workbook with multiple sheets and combining them into one dataframe", + "title": "46  Directory interactions", + "section": "46.7 Running other files", + "text": "46.7 Running other files\n\nsource()\nTo run one R script from another R script, you can use the source() command (from base R).\n\nsource(here(\"scripts\", \"cleaning_scripts\", \"clean_testing_data.R\"))\n\nThis is equivalent to viewing the above R script and clicking the “Source” button in the upper-right of the script. This will execute the script but will do it silently (no output to the R console) unless specifically intended. See the page on [Interactive console] for examples of using source() to interact with a user via the R console in question-and-answer mode.\n\n\n\n\n\n\n\n\n\n\n\nrender()\nrender() is a variation on source() most often used for R markdown scripts. You provide the input = which is the R markdown file, and also the output_format = (typically either “html_document”, “pdf_document”, “word_document”, ““)\nSee the page on Reports with R Markdown for more details. Also see the documentation for render() here or by entering ?render.\n\n\nRun files in a directory\nYou can create a for loop and use it to source() every file in a directory, as identified with dir().\n\nfor(script in dir(here(\"scripts\"), pattern = \".R$\")) { # for each script name in the R Project's \"scripts\" folder (with .R extension)\n source(here(\"scripts\", script)) # source the file with the matching name that exists in the scripts folder\n}\n\nIf you only want to run certain scripts, you can identify them by name like this:\n\nscripts_to_run <- c(\n \"epicurves.R\",\n \"demographic_tables.R\",\n \"survival_curves.R\"\n)\n\nfor(script in scripts_to_run) {\n source(here(\"scripts\", script))\n}\n\nHere is a comparison of the fs and base R functions.\n\n\nImport files in a directory\nSee the page on [Import and export] for importing and exporting individual files.\nAlso see the Import and export page for methods to automatically import the most recent file, based on a date in the file name or by looking at the file meta-data.\nSee the page on [Iteration, loops, and lists] for an example with the package purrr demonstrating:\n\nSplitting a data frame and saving it out as multiple CSV files.\n\nSplitting a data frame and saving each part as a separate sheet within one Excel workbook.\n\nImporting multiple CSV files and combining them into one data frame.\n\nImporting an Excel workbook with multiple sheets and combining them into one data frame.", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#base-r", "href": "new_pages/directories.html#base-r", - "title": "45  Directory interactions", - "section": "45.8 base R", - "text": "45.8 base R\nSee below the functions list.files() and dir(), which perform the same operation of listing files within a specified directory. You can specify ignore.case = or a specific pattern to look for.\n\nlist.files(path = here(\"data\"))\n\nlist.files(path = here(\"data\"), pattern = \".csv\")\n# dir(path = here(\"data\"), pattern = \".csv\")\n\nlist.files(path = here(\"data\"), pattern = \"evd\", ignore.case = TRUE)\n\nIf a file is currently “open”, it will display in your folder with a tilde in front, like “~$hospital_linelists.xlsx”.", + "title": "46  Directory interactions", + "section": "46.8 base R", + "text": "46.8 base R\nSee below the functions list.files() and dir(), which perform the same operation of listing files within a specified directory. You can specify ignore.case = or a specific pattern to look for.\n\nlist.files(path = here(\"data\"))\n\nlist.files(path = here(\"data\"), pattern = \".csv\")\n# dir(path = here(\"data\"), pattern = \".csv\")\n\nlist.files(path = here(\"data\"), pattern = \"evd\", ignore.case = TRUE)\n\nIf a file is currently “open”, it will display in your folder with a tilde in front, like “~$hospital_linelists.xlsx”.", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/directories.html#resources", "href": "new_pages/directories.html#resources", - "title": "45  Directory interactions", - "section": "45.9 Resources", - "text": "45.9 Resources\nhttps://cran.r-project.org/web/packages/fs/vignettes/function-comparisons.html", + "title": "46  Directory interactions", + "section": "46.9 Resources", + "text": "46.9 Resources\nComparison of fs functions, base R and shell commands", "crumbs": [ "Miscellaneous", - "45  Directory interactions" + "46  Directory interactions" ] }, { "objectID": "new_pages/collaboration.html", "href": "new_pages/collaboration.html", - "title": "46  Version control and collaboration with Git and Github", + "title": "47  Version control and collaboration with Git and Github", "section": "", - "text": "46.1 What is Git?\nGit is a version control software that allows tracking changes in a folder. It can be used like the “track change” option in Word, LibreOffice or Google docs, but for all types of files. It is one of the most powerful and most used options for version control.\nWhy have I never heard of it? - While people with a developer background routinely learn to use version control software (Git, Mercurial, Subversion or others), few of us from quantitative disciplines are taught these skills. Consequently, most epidemiologists never hear of it during their studies, and have to learn it on the fly.\nWait, I heard of Github, is it the same? - Not exactly, but you often use them together, and we will show you how to. In short:\nSo you could use the client/interface Github Desktop, which uses Git in the background to manage your files, both locally on your computer, and remotely on a Github server.", + "text": "47.1 What is Git?\nGit is a version control software that allows tracking changes in a folder. It can be used like the “track change” option in Word, LibreOffice or Google docs, but for all types of files. It is one of the most powerful and most used options for version control.\nWhy have I never heard of it? - While people with a developer background routinely learn to use version control software (Git, Mercurial, Subversion or others), few of us from quantitative disciplines are taught these skills. Consequently, most epidemiologists never hear of it during their studies, and have to learn it on the job.\nWait, I heard of Github, is it the same? - Not exactly, but you often use them together, and we will show you how to. In short:\nSo you could use the client/interface Github Desktop, which uses Git in the background to manage your files, both locally on your computer, and remotely on a Github server.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#what-is-git", "href": "new_pages/collaboration.html#what-is-git", - "title": "46  Version control and collaboration with Git and Github", + "title": "47  Version control and collaboration with Git and Github", "section": "", "text": "Git is the version control system, a piece of software. You can use it locally on your computer or to synchronize a folder with a host website. By default, one uses a terminal to give Git instructions in command-line.\nYou can use a Git client/interface to avoid the command-line and perform the same actions (at least for the simple, super common ones).\nIf you want to store your folder in a host website to collaborate with others, you may create an account at Github, Gitlab, Bitbucket or others.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#why-use-the-combo-git-and-github", "href": "new_pages/collaboration.html#why-use-the-combo-git-and-github", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.2 Why use the combo Git and Github?", - "text": "46.2 Why use the combo Git and Github?\nUsing Git facilitates:\n\nArchiving documented versions with incremental changes so that you can easily revert backwards to any previous state\nHaving parallel branches, i.e. developing/“working” versions with structured ways to integrate the changes after review\n\nThis can be done locally on your computer, even if you don’t collaborate with other people. Have you ever:\n\nregretted having deleted a section of code, only to realize two months later that you actually needed it?\ncome back on a project that had been on pause and attempted to remember whether you had made that tricky modification in one of the models?\nhad a file model_1.R and another file model_1_test.R and a file model_1_not_working.R to try things out?\nhad a file report.Rmd, a file report_full.Rmd, a file report_true_final.Rmd, a file report_final_20210304.Rmd, a file report_final_20210402.Rmd and cursed your archiving skills?\n\nGit will help with all that, and is worth to learn for that alone.\nHowever, it becomes even more powerful when used with a online repository such as Github to support collaborative projects. This facilitates:\n\nCollaboration: others can review, comment on, and accept/decline changes\nSharing your code, data, and outputs, and invite feedback from the public (or privately, with your team)\n\nand avoids:\n\n“Oops, I forgot to send the last version and now you need to redo two days worth of work on this new file”\nMina, Henry and Oumar all worked at the same time on one script and need to manually merge their changes\nTwo people try to modify the same file on Dropbox and Sharepoint and this creates a synchronization error.\n\n\nThis sounds complicated, I am not a programmer\nIt can be. Examples of advanced uses can be quite scary. However, much like R, or even Excel, you don’t need to become an expert to reap the benefits of the tool. Learning a small number of functions and notions lets you track your changes, synchronize your files on a online repository and collaborate with your colleagues in a very short amount of time.\nDue to the learning curve, emergency context may not be the best of time to learn these tools. But learning can be achieved by steps. Once you acquire a couple of notions, your workflow can be quite efficient and fast. If you are not working on a project where collaborating with people through Git is a necessity, it is actually a good time to get confident using it in solo before diving in collaboration.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.2 Why use the combo Git and Github?", + "text": "47.2 Why use the combo Git and Github?\nUsing Git facilitates:\n\nArchiving documented versions with incremental changes so that you can easily revert backwards to any previous state.\nHaving parallel branches, i.e. developing/“working” versions with structured ways to integrate the changes after review.\n\nThis can be done locally on your computer, even if you don’t collaborate with other people. Have you ever:\n\nRegretted having deleted a section of code, only to realize two months later that you actually needed it?\nCome back on a project that had been on pause and attempted to remember whether you had made that tricky modification in one of the models?\nHad a file model_1.R and another file model_1_test.R and a file model_1_not_working.R to try things out?\nHad a file report.Rmd, a file report_full.Rmd, a file report_true_final.Rmd, a file report_final_20210304.Rmd, a file report_final_20210402.Rmd and cursed your archiving skills?\n\nGit will help with all that, and is worth to learn for that alone.\nHowever, it becomes even more powerful when used with a online repository such as Github to support collaborative projects. This facilitates:\n\nCollaboration: others can review, comment on, and accept/decline changes.\nSharing your code, data, and outputs, and invite feedback from the public (or privately, with your team).\n\nand avoids:\n\n“Oops, I forgot to send the last version and now you need to redo two days worth of work on this new file”.\nMina, Henry and Oumar all worked at the same time on one script and need to manually merge their changes.\nTwo people try to modify the same file on Dropbox and Sharepoint and this creates a synchronization error.\n\n\nThis sounds complicated, I am not a programmer\nIt can be. Examples of advanced uses can be quite scary. However, much like R, or even Excel, you don’t need to become an expert to reap the benefits of the tool. Learning a small number of functions and notions lets you track your changes, synchronize your files on a online repository and collaborate with your colleagues in a very short amount of time.\nDue to the learning curve, emergency context may not be the best of time to learn these tools. But learning can be achieved by steps. Once you acquire a couple of notions, your workflow can be quite efficient and fast. If you are not working on a project where collaborating with people through Git is a necessity, it is actually a good time to get confident using it in solo before diving in collaboration.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#setup", "href": "new_pages/collaboration.html#setup", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.3 Setup", - "text": "46.3 Setup\n\nInstall Git\nGit is the engine behind the scenes on your computer, which tracks changes, branches (versions), merges, and reverting. You must first install Git from https://git-scm.com/downloads.\n\n\nInstall an interface (optional but recommended)\nGit has its own language of commands, which can be typed into a command line terminal. However, there are many clients/interfaces and as non-developpers, in your day-to-day use, you will rarely need to interact with Git directly and interface usually provide nice visualisation tools for file modifications or branches.\nMany options exist, on all OS, from beginner friendly to more complex ones. Good options for beginners include the RStudio Git pane and Github Desktop, which we will showcase in this chapter. Intermediate (more powerfull, but more complex) options include Source Tree, Gitkracken, Smart Git and others.\nQuick explanation on Git clients.\nNote: since interfaces actually all use Git internally, you can try several of them, switch from one to another on a given project, use the console punctually for an action your interface does not support, or even perform any number of actions online on Github.\nAs noted below, you may occasionally have to write Git commands into a terminal such as the RStudio terminal pane (a tab adjacent to the R Console) or the Git Bash terminal.\n\n\nGithub account\nSign-up for a free account at github.com.\nYou may be offered to set-up two-factor authentication with an app on your phone. Read more in the Github help documents.\nIf you use Github Desktop, you can enter your Gitub credentials after installation following these steps. If you don’t do it know, credentials will be asked later when you try to clone a project from Github.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.3 Setup", + "text": "47.3 Setup\n\nInstall Git\nGit is the engine behind the scenes on your computer, which tracks changes, branches (versions), merges, and reverting. You must first install Git from https://git-scm.com/downloads.\n\n\nInstall an interface (optional but recommended)\nGit has its own language of commands, which can be typed into a command line terminal. However, there are many clients/interfaces and as non-developpers, in your day-to-day use, you will rarely need to interact with Git directly and interface usually provide nice visualisation tools for file modifications or branches.\nMany options exist, on all OS, from beginner friendly to more complex ones. Good options for beginners include the RStudio Git pane and Github Desktop, which we will showcase in this chapter. Intermediate (more powerfull, but more complex) options include Source Tree, Gitkracken, Smart Git and others.\nQuick explanation on Git clients.\nNote: since interfaces actually all use Git internally, you can try several of them, switch from one to another on a given project, use the console punctually for an action your interface does not support, or even perform any number of actions online on Github.\nAs noted below, you may occasionally have to write Git commands into a terminal such as the RStudio terminal pane (a tab adjacent to the R Console) or the Git Bash terminal.\n\n\nGithub account\nSign-up for a free account at github.com.\nYou may be offered to set-up two-factor authentication with an app on your phone. Read more in the Github help documents.\nIf you use Github Desktop, you can enter your Gitub credentials after installation following these steps. If you don’t do it know, credentials will be asked later when you try to clone a project from Github.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#vocabulary-concepts-and-basic-functions", "href": "new_pages/collaboration.html#vocabulary-concepts-and-basic-functions", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.4 Vocabulary, concepts and basic functions", - "text": "46.4 Vocabulary, concepts and basic functions\nAs when learning R, there is a bit of vocabulary to remember to understand Git. Here are the basics to get you going / interactive tutorial. In the next sections, we will show how to use interfaces, but it is good to have the vocabulary and concepts in mind, to build your mental model, and as you’ll need them when using interfaces anyway.\n\nRepository\nA Git repository (“repo”) is a folder that contains all the sub-folders and files for your project (data, code, images, etc.) and their revision histories. When you begin tracking changes in the repository with it, Git will create a hidden folder that contains all tracking information. A typical Git repository is your R Project folder (see handbook page on R projects).\nWe will show how to create (initialize) a Git repository from Github, Github Desktop or Rstudio in the next sections.\n\n\nCommits\nA commit is a snapshot of the project at a given time. When you make a change to the project, you will make a new commit to track the changes (the delta) made to your files. For example, perhaps you edited some lines of code and updated a related dataset. Once your changes are saved, you can bundle these changes together into one “commit”.\nEach commit has a unique ID (a hash). For version control purposes, you can revert your project back in time based on commits, so it is best to keep them relatively small and coherent. You will also attach a brief description of the changes called the “commit message”.\nStaged changes? To stage changes is to add them to the staging area in preparation for the next commit. The idea is that you can finely decide which changes to include in a given commit. For example, if you worked on model specification in one script, and later on a figure in another script, it would make sense to have two different commits (it would be easier in case you wanted to revert the changes on the figure but not the model).\n\n\nBranches\nA branch represents an independent line of changes in your repo, a parallel, alternate version of your project files.\nBranches are useful to test changes before they are incorporated into the main branch, which is usually the primary/final/“live” version of your project. When you are done experimenting on a branch, you can bring the changes into your main branch, by merging it, or delete it, if the changes were not so successful.\nNote: you do not have to collaborate with other people to use branches, nor need to have a remote online repository.\n\n\nLocal and remote repositories\nTo clone is to create a copy of a Git repository in another place.\nFor example, you can clone a online repository from Github locally on your computer, or begin with a local repository and clone it online to Github.\nWhen you have cloned a repository, the project files exist in two places:\n\nthe LOCAL repository on your physical computer. This is where you make the actual changes to the files/code.\nthe REMOTE, online repository: the versions of your project files in the Github repository (or on any other web host).\n\nTo synchronize these repositories, we will use more functions. Indeed, unlike Sharepoint, Dropbox or other synchronizing software, Git does not automatically update your local repository based or what’s online, or vice-versa. You get to choose when and how to synchronize.\n\ngit fetch downloads the new changes from the remote repository but does not change your local repository. Think of it as checking the state of the remote repository.\ngit pull downloads the new changes from the remote repositories and update your local repository.\nWhen you have made one or several commits locally, you can git push the commits to the remote repository. This sends your changes on Github so that other people can see and pull them if they want to.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.4 Vocabulary, concepts and basic functions", + "text": "47.4 Vocabulary, concepts and basic functions\nAs when learning R, there is a bit of vocabulary to remember to understand Git. Here are the basics to get you going / interactive tutorial. In the next sections, we will show how to use interfaces, but it is good to have the vocabulary and concepts in mind, to build your mental model, and as you’ll need them when using interfaces anyway.\n\nRepository\nA Git repository (“repo”) is a folder that contains all the sub-folders and files for your project (data, code, images, etc.) and their revision histories. When you begin tracking changes in the repository with it, Git will create a hidden folder that contains all tracking information. A typical Git repository is your R Project folder (see handbook page on R projects).\nWe will show how to create (initialize) a Git repository from Github, Github Desktop or Rstudio in the next sections.\n\n\nCommits\nA commit is a snapshot of the project at a given time. When you make a change to the project, you will make a new commit to track the changes (the delta) made to your files. For example, perhaps you edited some lines of code and updated a related dataset. Once your changes are saved, you can bundle these changes together into one “commit”.\nEach commit has a unique ID (a hash). For version control purposes, you can revert your project back in time based on commits, so it is best to keep them relatively small and coherent. You will also attach a brief description of the changes called the “commit message”.\nStaged changes? To stage changes is to add them to the staging area in preparation for the next commit. The idea is that you can finely decide which changes to include in a given commit. For example, if you worked on model specification in one script, and later on a figure in another script, it would make sense to have two different commits (it would be easier in case you wanted to revert the changes on the figure but not the model).\n\n\nBranches\nA branch represents an independent line of changes in your repo, a parallel, alternate version of your project files.\nBranches are useful to test changes before they are incorporated into the main branch, which is usually the primary/final/“live” version of your project. When you are done experimenting on a branch, you can bring the changes into your main branch, by merging it, or delete it, if the changes were not so successful.\nNote: you do not have to collaborate with other people to use branches, nor need to have a remote online repository.\n\n\nLocal and remote repositories\nTo clone is to create a copy of a Git repository in another place.\nFor example, you can clone a online repository from Github locally on your computer, or begin with a local repository and clone it online to Github.\nWhen you have cloned a repository, the project files exist in two places:\n\nthe LOCAL repository on your physical computer. This is where you make the actual changes to the files/code.\nthe REMOTE, online repository: the versions of your project files in the Github repository (or on any other web host).\n\nTo synchronize these repositories, we will use more functions. Indeed, unlike Sharepoint, Dropbox or other synchronizing software, Git does not automatically update your local repository based or what’s online, or vice-versa. You get to choose when and how to synchronize.\n\ngit fetch downloads the new changes from the remote repository but does not change your local repository. Think of it as checking the state of the remote repository.\ngit pull downloads the new changes from the remote repositories and update your local repository.\nWhen you have made one or several commits locally, you can git push the commits to the remote repository. This sends your changes on Github so that other people can see and pull them if they want to.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#get-started-create-a-new-repository", "href": "new_pages/collaboration.html#get-started-create-a-new-repository", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.5 Get started: create a new repository", - "text": "46.5 Get started: create a new repository\nThere are many ways to create new repositories. You can do it from the console, from Github, from an interface.\nTwo general approaches to set-up are:\n\nCreate a new R Project from an existing or new Github repository (preferred for beginners), or\nCreate a Github repository for an existing R project\n\n\nStart-up files\nWhen you create a new repository, you can optionally create all of the below files, or you can add them to your repository at a later stage. They would typically live in the “root” folder of the repository.\n\nA README file is a file that someone can read to understand why your project exists and what else they should know to use it. It will be empty at first, but you should complete it later.\nA .gitignore file is a text file where each line would contain folders or files that Git should ignore (not track changes). Read more about it and see examples here.\nYou can choose a license for your work, so that other people know under which conditions they can use or reproduce your work. For more information, see the Creative Commons licenses.\n\n\n\nCreate a new repository in Github\nTo create a new repository, log into Github and look for the green button to create a new repository. This now empty repository can be cloned locally to your computer (see next section).\n\n\n\n\n\n\n\n\n\nYou must choose if you want your repository to be public (visible to everyone on the internet) or private (only visible to those with permission). This has important implications if your data are sensitive. If your repository is private you will encounter some quotas in advanced special circumstances, such as if you are using Github actions to automatically run your code in the cloud.\n\n\nClone from a Github repository\nYou can clone an existing Github repository to create a new local R project on your computer.\nThe Github repository could be one that already exists and contains content, or could be an empty repository that you just created. In this latter case you are essentially creating the Github repo and local R project at the same time (see instructions above).\nNote: if you do not have contributing rights on a Github repository, it is possible to first fork the repository to your profile, and then proceed with the other actions. Forking is explained at the end of this chapter, but we recommend that you read the other sections first.\nStep 1: Navigate in Github to the repository, click on the green “Code” button and copy the HTTPS clone URL (see image below)\n\n\n\n\n\n\n\n\n\nThe next step can be performed in any interface. We will illustrate with Rstudio and Github desktop.\n\nIn Rstudio\nIn RStudio, start a new R project by clicking File > New Project > Version Control > Git\n\nWhen prompted for the “Repository URL”, paste the HTTPS URL from Github\n\nAssign the R project a short, informative name\n\nChoose where the new R Project will be saved locally\n\nCheck “Open in new session” and click “Create project”\n\nYou are now in a new, local, RStudio project that is a clone of the Github repository. This local project and the Github repository are now linked.\n\n\nIn Github Desktop\n\nClick on File > Clone a repository\nSelect the URL tab\nPaste the HTTPS URL from Github in the first box\nSelect the folder in which you want to have your local repository\nClick “CLONE”\n\n\n\n\n\n\n\n\n\n\n\n\n\nNew Github repo from existing R project\nAn alternative setup scenario is that you have an existing R project with content, and you want to create a Github repository for it.\n\nCreate a new, empty Github repository for the project (see instructions above)\n\nClone this repository locally (see HTTPS instructions above)\n\nCopy all the content from your pre-existing R project (codes, data, etc.) into this new empty, local, repository (e.g. use copy and paste).\n\nOpen your new project in RStudio, and go to the Git pane. The new files should register as file changes, now tracked by Git. Therefore, you can bundle these changes as a commit and push them up to Github. Once pushed, the repository on Github will reflect all the files.\n\nSee the Github workflow section below for details on this process.\n\n\nWhat does it look like now?\n\nIn RStudio\nOnce you have cloned a Github repository to a new R project, you now see in RStudio a “Git” tab. This tab appears in the same RStudio pane as your R Environment:\n\n\n\n\n\n\n\n\n\nPlease note the buttons circled in the image above, as they will be referenced later (from left to right):\n\nButton to commit the saved file changes to the local branch (this will open a new window)\nBlue arrow to pull (update your local version of the branch with any changes made on the remote/Github version of that branch)\nGreen arrow to push (send any commits/changes for your local version of the branch to the remote/Github version of that branch)\nThe Git tab in RStudio\nButton to create a NEW branch using whichever local branch is shown to the right as the base. You almost always want to branch off from the main branch (after you first pull to update the main branch)\nThe branch you are currently working in\nChanges you made to code or other files will appear below\n\n\n\nIn Github Desktop\nGithub Desktop is an independent application that allows you to manage all your repositories. When you open it, the interface allows you to choose the repository you want to work on, and then to perform basic Git actions from there.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.5 Get started: create a new repository", + "text": "47.5 Get started: create a new repository\nThere are many ways to create new repositories. You can do it from the console, from Github, from an interface.\nTwo general approaches to set-up are:\n\nCreate a new R Project from an existing or new Github repository (preferred for beginners), or,\nCreate a Github repository for an existing R project.\n\n\nStart-up files\nWhen you create a new repository, you can optionally create all of the below files, or you can add them to your repository at a later stage. They would typically live in the “root” folder of the repository.\n\nA README file is a file that someone can read to understand why your project exists and what else they should know to use it. It will be empty at first, but you should complete it later.\nA .gitignore file is a text file where each line would contain folders or files that Git should ignore (not track changes). Read more about it and see examples here.\nYou can choose a license for your work, so that other people know under which conditions they can use or reproduce your work. For more information, see the Creative Commons licenses.\n\n\n\nCreate a new repository in Github\nTo create a new repository, log into Github and look for the green button to create a new repository. This now empty repository can be cloned locally to your computer (see next section).\n\n\n\n\n\n\n\n\n\nYou must choose if you want your repository to be public (visible to everyone on the internet) or private (only visible to those with permission). This has important implications if your data are sensitive. If your repository is private you will encounter some quotas in advanced special circumstances, such as if you are using Github actions to automatically run your code in the cloud.\n\n\nClone from a Github repository\nYou can clone an existing Github repository to create a new local R project on your computer.\nThe Github repository could be one that already exists and contains content, or could be an empty repository that you just created. In this latter case you are essentially creating the Github repo and local R project at the same time (see instructions above).\nNote: if you do not have contributing rights on a Github repository, it is possible to first fork the repository to your profile, and then proceed with the other actions. Forking is explained at the end of this chapter, but we recommend that you read the other sections first.\nStep 1: Navigate in Github to the repository, click on the green “Code” button and copy the HTTPS clone URL (see image below)\n\n\n\n\n\n\n\n\n\nThe next step can be performed in any interface. We will illustrate with Rstudio and Github desktop.\n\nIn Rstudio\nIn RStudio, start a new R project by clicking File > New Project > Version Control > Git.\n\nWhen prompted for the “Repository URL”, paste the HTTPS URL from Github.\n\nAssign the R project a short, informative name.\n\nChoose where the new R Project will be saved locally.\n\nCheck “Open in new session” and click “Create project”.\n\nYou are now in a new, local, RStudio project that is a clone of the Github repository. This local project and the Github repository are now linked.\n\n\nIn Github Desktop\n\nClick on File > Clone a repository.\nSelect the URL tab.\nPaste the HTTPS URL from Github in the first box.\nSelect the folder in which you want to have your local repository.\nClick “CLONE”.\n\n\n\n\n\n\n\n\n\n\n\n\n\nNew Github repo from existing R project\nAn alternative setup scenario is that you have an existing R project with content, and you want to create a Github repository for it.\n\nCreate a new, empty Github repository for the project (see instructions above).\n\nClone this repository locally (see HTTPS instructions above).\n\nCopy all the content from your pre-existing R project (codes, data, etc.) into this new empty, local, repository (e.g. use copy and paste).\n\nOpen your new project in RStudio, and go to the Git pane. The new files should register as file changes, now tracked by Git. Therefore, you can bundle these changes as a commit and push them up to Github. Once pushed, the repository on Github will reflect all the files.\n\nSee the Github workflow section below for details on this process.\n\n\nWhat does it look like now?\n\nIn RStudio\nOnce you have cloned a Github repository to a new R project, you now see in RStudio a “Git” tab. This tab appears in the same RStudio pane as your R Environment:\n\n\n\n\n\n\n\n\n\nPlease note the buttons circled in the image above, as they will be referenced later (from left to right):\n\nButton to commit the saved file changes to the local branch (this will open a new window).\nBlue arrow to pull (update your local version of the branch with any changes made on the remote/Github version of that branch).\nGreen arrow to push (send any commits/changes for your local version of the branch to the remote/Github version of that branch)\nThe Git tab in RStudio.\nButton to create a NEW branch using whichever local branch is shown to the right as the base. You almost always want to branch off from the main branch (after you first pull to update the main branch).\nThe branch you are currently working in.\nChanges you made to code or other files will appear below.\n\n\n\nIn Github Desktop\nGithub Desktop is an independent application that allows you to manage all your repositories. When you open it, the interface allows you to choose the repository you want to work on, and then to perform basic Git actions from there.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#git-github-workflow", "href": "new_pages/collaboration.html#git-github-workflow", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.6 Git + Github workflow", - "text": "46.6 Git + Github workflow\n\nProcess overview\nOnce you have completed the setup (described above), you will have a Github repo that is connected (cloned) to a local R project. The main branch (created by default) is the so-called “live” version of all the files. When you want to make modifications, it is a good practice to create a new branch from the main branch (like “Make a Copy”). This is a typical workflow in Git because creating a branch is easy and fast.\nA typical workflow is as follow:\n\nMake sure that your local repository is up-to-date, update it if not\nGo to the branch you were working on previously, or create a new branch to try out some things\nWork on the files locally on your computer, make one or several commits to this branch\nUpdate the remote version of the branch with your changes (push)\nWhen you are satisfied with your branch, you can merge the online version of the working branch into the online “main” branch to transfer the changes\n\nOther team members may be doing the same thing with their own branches, or perhaps contributing commits into your working branch as well.\nWe go through the above process step-by-step in more detail below. Here is a schematic we’ve developed - it’s in the format of a two-way table so it should help epidemiologists understand.\n\n\n\n\n\n\n\n\n\nHere’s another diagram.\nNote: until recently, the term “master” branch was used, but it is now referred to as “main” branch.\n\n\n\n\n\n\n\n\n\nImage source", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.6 Git + Github workflow", + "text": "47.6 Git + Github workflow\n\nProcess overview\nOnce you have completed the setup (described above), you will have a Github repo that is connected (cloned) to a local R project. The main branch (created by default) is the so-called “live” version of all the files. When you want to make modifications, it is a good practice to create a new branch from the main branch (like “Make a Copy”). This is a typical workflow in Git because creating a branch is easy and fast.\nA typical workflow is as follow:\n\nMake sure that your local repository is up-to-date, update it if not.\nGo to the branch you were working on previously, or create a new branch to try out some things.\nWork on the files locally on your computer, make one or several commits to this branch.\nUpdate the remote version of the branch with your changes (push).\nWhen you are satisfied with your branch, you can merge the online version of the working branch into the online “main” branch to transfer the changes.\n\nOther team members may be doing the same thing with their own branches, or perhaps contributing commits into your working branch as well.\nWe go through the above process step-by-step in more detail below. Here is a schematic we’ve developed - it’s in the format of a two-way table so it should help epidemiologists understand.\n\n\n\n\n\n\n\n\n\nHere’s another diagram.\nNote: until recently, the term “master” branch was used, but it is now referred to as “main” branch.\n\n\n\n\n\n\n\n\n\nImage source.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#create-a-new-branch", "href": "new_pages/collaboration.html#create-a-new-branch", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.7 Create a new branch", - "text": "46.7 Create a new branch\nWhen you select a branch to work on, Git resets your working directory the way it was the last time you were on this branch.\n\nIn Rstudio Git pane\nEnsure you are in the “main” branch, and then click on the purple icon to create a new branch (see image above).\n\nYou will be prompted to name your branch with a one-word descriptive name (can use underscores if needed).\nYou will see that locally, you are still in the same R project, but you are no longer working on the “main” branch.\nOnce created, the new branch will also appear in the Github website as a branch.\n\nYou can visualize branches in the Git Pane in Rstudio after clicking on “History”\n\n\n\n\n\n\n\n\n\n\n\nIn Github Desktop\nThe process is very much similar, you are prompted to give your branch a name. After, you will be prompted to “Publish you branch to Github” to make the new branch appear in the remote repo as well.\n\n\n\n\n\n\n\n\n\n\n\nIn console\nWhat is actually happening behind the scenes is that you create a new branch with git branch, then go to the branch with git checkout (i.e. tell Git that your next commits will occur there). From your git repository:\n\ngit branch my-new-branch # Create the new branch branch\ngit checkout my-new-branch # Go to the branch\ngit checkout -b my-new-branch # Both at once (shortcut)\n\nFor more information about using the console, see the section on Git commands at the end.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.7 Create a new branch", + "text": "47.7 Create a new branch\nWhen you select a branch to work on, Git resets your working directory the way it was the last time you were on this branch.\n\nIn Rstudio Git pane\nEnsure you are in the “main” branch, and then click on the purple icon to create a new branch (see image above).\n\nYou will be prompted to name your branch with a one-word descriptive name (can use underscores if needed).\nYou will see that locally, you are still in the same R project, but you are no longer working on the “main” branch.\nOnce created, the new branch will also appear in the Github website as a branch.\n\nYou can visualize branches in the Git Pane in Rstudio after clicking on “History”\n\n\n\n\n\n\n\n\n\n\n\nIn Github Desktop\nThe process is very much similar, you are prompted to give your branch a name. After, you will be prompted to “Publish you branch to Github” to make the new branch appear in the remote repo as well.\n\n\n\n\n\n\n\n\n\n\n\nIn console\nWhat is actually happening behind the scenes is that you create a new branch with git branch, then go to the branch with git checkout (i.e. tell Git that your next commits will occur there). From your git repository:\n\ngit branch my-new-branch # Create the new branch branch\ngit checkout my-new-branch # Go to the branch\ngit checkout -b my-new-branch # Both at once (shortcut)\n\nFor more information about using the console, see the section on Git commands at the end.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#commit-changes", "href": "new_pages/collaboration.html#commit-changes", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.8 Commit changes", - "text": "46.8 Commit changes\nNow you can edit code, add new files, update datasets, etc.\nEvery one of your changes is tracked, once the respective file is saved. Changed files will appear in the RStudio Git tab, in Github Desktop, or using the command git status in the terminal (see below).\nWhenever you make substantial changes (e.g. adding or updating a section of code), pause and commit those changes. Think of a commit as a “batch” of changes related to a common purpose. You can always continue to revise a file after having committed changes on it.\nAdvice on commits: generally, it is better to make small commits, that can be easily reverted if a problem arises, to commit together modifications related to a common purpose. To achieve this, you will find that you should commit often. At the beginning, you’ll probably forget to commit often, but then the habit kicks in.\n\nIn Rstudio\nThe example below shows that, since the last commit, the R Markdown script “collaboration.Rmd” has changed, and several PNG images were added.\n\n\n\n\n\n\n\n\n\nYou might be wondering what the yellow, blue, green, and red squares next to the file names represent. Here is a snapshot from the RStudio cheatsheet that explains their meaning. Note that changes with yellow “?” can still be staged, committed, and pushed.\n\n\n\n\n\n\n\n\n\n\nPress the “Commit” button in the Git tab, which will open a new window (shown below)\nClick on a file name in the upper-left box\nReview the changes you made to that file (highlighted below in green or red)\n“Stage” the file, which will include those changes in the commit. Do this by checking the box next to the file name. Alternatively, you can highlight multiple file names and then click “Stage”\nWrite a commit message that is short but descriptive (required)\nPress the “Commit” button. A pop-up box will appear showing success or an error message.\n\nNow you can make more changes and more commits, as many times as you would like\n\n\n\n\n\n\n\n\n\n\n\nIn Github Desktop\nYou can see the list of the files that were changed on the left. If you select a text file, you will see a summary of the modifications that were made in the right pane (the view will not work on more complex files like .docs or .xlsx).\nTo stage the changes, just tick the little box near file names. When you have selected the files you want to add to this commit, give the commit a name, optionally a description and then click on the commit button.\n\n\n\n\n\n\n\n\n\n\n\nIn console\nThe two functions used behind the scenes are git add to select/stage files and git commit to actually do the commit.\n\ngit status # see the changes \n\ngit add new_pages/collaboration.Rmd # select files to commit (= stage the changes)\n\ngit commit -m \"Describe commit from Github Desktop\" # commit the changes with a message\n\ngit log # view information on past commits\n\n\n\nAmend a previous commit\nWhat happens if you commit some changes, carry on working, and realize that you made changes that should “belong” to the past commit (in your opinion). Fear not! You can append these changes to your previous commit.\nIn Rstudio, it should be pretty obvious as there is a “Amend previous commit” box on the same line as the COMMIT button.\nFor some unclear reason, the functionality has not been implemented as such in Github Desktop, but there is a (conceptually awkward but easy) way around. If you have committed but not pushed your changes yet, an “UNDO” button appears just under the COMMIT button. Click on it and it will revert your commit (but keep your staged files and your commit message). Save your changes, add new files to the commit if necessary and commit again.\nIn the console:\n\ngit add [YOUR FILES] # Stage your new changes\n\ngit commit --amend # Amend the previous commit\n\ngit commit --amend -m \"An updated commit message\" # Amend the previous commit AND update the commit message\n\nNote: think before modifying commits that are already public and shared with your collaborators.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.8 Commit changes", + "text": "47.8 Commit changes\nNow you can edit code, add new files, update datasets, etc.\nEvery one of your changes is tracked, once the respective file is saved. Changed files will appear in the RStudio Git tab, in Github Desktop, or using the command git status in the terminal (see below).\nWhenever you make substantial changes (e.g. adding or updating a section of code), pause and commit those changes. Think of a commit as a “batch” of changes related to a common purpose. You can always continue to revise a file after having committed changes on it.\nAdvice on commits: generally, it is better to make small commits, that can be easily reverted if a problem arises, to commit together modifications related to a common purpose. To achieve this, you will find that you should commit often. At the beginning, you’ll probably forget to commit often, but then the habit kicks in.\n\nIn Rstudio\nThe example below shows that, since the last commit, the R Markdown script “collaboration.Rmd” has changed, and several PNG images were added.\n\n\n\n\n\n\n\n\n\nYou might be wondering what the yellow, blue, green, and red squares next to the file names represent. Here is a snapshot from the RStudio cheatsheet that explains their meaning. Note that changes with yellow “?” can still be staged, committed, and pushed.\n\n\n\n\n\n\n\n\n\n\nPress the “Commit” button in the Git tab, which will open a new window (shown below).\nClick on a file name in the upper-left box.\nReview the changes you made to that file (highlighted below in green or red).\n“Stage” the file, which will include those changes in the commit. Do this by checking the box next to the file name. Alternatively, you can highlight multiple file names and then click “Stage”.\nWrite a commit message that is short but descriptive (required).\nPress the “Commit” button. A pop-up box will appear showing success or an error message.\n\nNow you can make more changes and more commits, as many times as you would like\n\n\n\n\n\n\n\n\n\n\n\nIn Github Desktop\nYou can see the list of the files that were changed on the left. If you select a text file, you will see a summary of the modifications that were made in the right pane (the view will not work on more complex files like .docs or .xlsx).\nTo stage the changes, just tick the little box near file names. When you have selected the files you want to add to this commit, give the commit a name, optionally a description and then click on the commit button.\n\n\n\n\n\n\n\n\n\n\n\nIn console\nThe two functions used behind the scenes are git add to select/stage files and git commit to actually do the commit.\n\ngit status # see the changes \n\ngit add new_pages/collaboration.Rmd # select files to commit (= stage the changes)\n\ngit commit -m \"Describe commit from Github Desktop\" # commit the changes with a message\n\ngit log # view information on past commits\n\n\n\nAmend a previous commit\nWhat happens if you commit some changes, carry on working, and realize that you made changes that should “belong” to the past commit (in your opinion). Fear not! You can append these changes to your previous commit.\nIn Rstudio, it should be pretty obvious as there is a “Amend previous commit” box on the same line as the COMMIT button.\nFor some unclear reason, the functionality has not been implemented as such in Github Desktop, but there is a (conceptually awkward but easy) way around. If you have committed but not pushed your changes yet, an “UNDO” button appears just under the COMMIT button. Click on it and it will revert your commit (but keep your staged files and your commit message). Save your changes, add new files to the commit if necessary and commit again.\nIn the console:\n\ngit add [YOUR FILES] # Stage your new changes\n\ngit commit --amend # Amend the previous commit\n\ngit commit --amend -m \"An updated commit message\" # Amend the previous commit AND update the commit message\n\nNote: think before modifying commits that are already public and shared with your collaborators.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#pull-and-push-changes-up-to-github", "href": "new_pages/collaboration.html#pull-and-push-changes-up-to-github", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.9 Pull and push changes up to Github", - "text": "46.9 Pull and push changes up to Github\n“First PULL, then PUSH”\nIt is good practice to fetch and pull before you begin working on your project, to update the branch version on your local computer with any changes that have been made to it in the remote/Github version.\nPULL often. Don’t hesitate. Always pull before pushing.\nWhen your changes are made and committed and you are happy with the state of your project, you can push your commits up to the remote/Github version of your branch.\nRince and repeat while you are working on the repository.\nNote: it is much easier to revert changes that were committed but not pushed (i.e. are still local) than to revert changes that were pushed to the remote repository (and perhaps already pulled by someone else), so it is better to push when you are done with introducing changes on the task that you were working on.\n\nIn Rstudio\nPULL - First, click the “Pull” icon (downward arrow) which fetches and pulls at the same time.\nPUSH - Clicking the green “Pull” icon (upward arrow). You may be asked to enter your Github username and password. The first time you are asked, you may need to enter two Git command lines into the Terminal:\n\ngit config –global user.email “you@example.com” (your Github email address), and\n\ngit config –global user.name “Your Github username”\n\nTo learn more about how to enter these commands, see the section below on Git commands.\nTIP: Asked to provide your password too often? See these chapters 10 & 11 of this tutorial to connect to a repository using a SSH key (more complicated)\n\n\nIn Github Desktop\nClick on the “Fetch origin” button to check if there are new commits on the remote repository.\n\n\n\n\n\n\n\n\n\nIf Git finds new commits on the remote repository, the button will change into a “Pull” button. Because the same button is used to push and pull, you cannot push your changes if you don’t pull before.\n\n\n\n\n\n\n\n\n\nYou can go to the “History” tab (near the “Changes” tab) to see all commits (yours and others). This is a nice way of acquainting yourself with what your collaborators did. You can read the commit message, the description if there is one, and compare the code of the two files using the diff pane.\n\n\n\n\n\n\n\n\n\nOnce all remote changes have been pulled, and at least one local change has been committed, you can push by clicking on the same button.\n\n\n\n\n\n\n\n\n\n\n\nConsole\nWithout surprise, the commands are fetch, pull and push.\n\ngit fetch # are there new commits in the remote directory?\ngit pull # Bring remote commits into your local branch\ngit push # Puch local commits of this branch to the remote branch\n\n\n\nI want to pull but I have local work\nThis can happen sometimes: you made some changes on your local repository, but the remote repository has commits that you didn’t pull.\nGit will refuse to pull because it might overwrite your changes. There are several strategies to keep your changes, well described in Happy Git with R, among which the two main ones are: - commit your changes, fetch remote changes, pull them in, resolve conflicts if needed (see section below), and push everything online - stash your changes, which sort of stores them aside, pull, unstash (restore), and then commit, solve any conflicts, and push.\nIf the files concerned by the remote changes and the files concerned by your local changes do not overlap, Git may solve conflicts automatically.\nIn Github Desktop, this can be done with buttons. To stash, go to Branch > Stash all changes.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.9 Pull and push changes up to Github", + "text": "47.9 Pull and push changes up to Github\n“First PULL, then PUSH”\nIt is good practice to fetch and pull before you begin working on your project, to update the branch version on your local computer with any changes that have been made to it in the remote/Github version.\nPULL often. Don’t hesitate. Always pull before pushing.\nWhen your changes are made and committed and you are happy with the state of your project, you can push your commits up to the remote/Github version of your branch.\nRince and repeat while you are working on the repository.\nNote: it is much easier to revert changes that were committed but not pushed (i.e. are still local) than to revert changes that were pushed to the remote repository (and perhaps already pulled by someone else), so it is better to push when you are done with introducing changes on the task that you were working on.\n\nIn Rstudio\nPULL - First, click the “Pull” icon (downward arrow) which fetches and pulls at the same time.\nPUSH - Clicking the green “Push” icon (upward arrow). You may be asked to enter your Github username and password. The first time you are asked, you may need to enter two Git command lines into the Terminal:\n\ngit config –global user.email “you@example.com” (your Github email address), and\n\ngit config –global user.name “Your Github username”\n\nTo learn more about how to enter these commands, see the section below on Git commands.\nTIP: Asked to provide your password too often? See these chapters 10 & 11 of this tutorial to connect to a repository using a SSH key (more complicated) .\n\n\nIn Github Desktop\nClick on the “Fetch origin” button to check if there are new commits on the remote repository.\n\n\n\n\n\n\n\n\n\nIf Git finds new commits on the remote repository, the button will change into a “Pull” button. Because the same button is used to push and pull, you cannot push your changes if you don’t pull before.\n\n\n\n\n\n\n\n\n\nYou can go to the “History” tab (near the “Changes” tab) to see all commits (yours and others). This is a nice way of acquainting yourself with what your collaborators did. You can read the commit message, the description if there is one, and compare the code of the two files using the diff pane.\n\n\n\n\n\n\n\n\n\nOnce all remote changes have been pulled, and at least one local change has been committed, you can push by clicking on the same button.\n\n\n\n\n\n\n\n\n\n\n\nConsole\nWithout surprise, the commands are fetch, pull and push.\n\ngit fetch # are there new commits in the remote directory?\ngit pull # Bring remote commits into your local branch\ngit push # Puch local commits of this branch to the remote branch\n\n\n\nI want to pull but I have local work\nThis can happen sometimes: you made some changes on your local repository, but the remote repository has commits that you didn’t pull.\nGit will refuse to pull because it might overwrite your changes. There are several strategies to keep your changes, well described in Happy Git with R, among which the two main ones are: - commit your changes, fetch remote changes, pull them in, resolve conflicts if needed (see section below), and push everything online - stash your changes, which sort of stores them aside, pull, unstash (restore), and then commit, solve any conflicts, and push.\nIf the files concerned by the remote changes and the files concerned by your local changes do not overlap, Git may solve conflicts automatically.\nIn Github Desktop, this can be done with buttons. To stash, go to Branch > Stash all changes.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#merge-branch-into-main", "href": "new_pages/collaboration.html#merge-branch-into-main", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.10 Merge branch into Main", - "text": "46.10 Merge branch into Main\nIf you have finished making changes, you can begin the process of merging those changes into the main branch. Depending on your situation, this may be fast, or you may have deliberate review and approval steps involving teammates.\n\nLocally in Github Desktop\nOne can merge branches locally using Github Desktop. First, go to (checkout) the branch that will be the recipient of the commits, in other words, the branch you want to update. Then go to the menu Branch > Merge into current branch and click. A box will allow you to select the branch you want to import from.\n\n\n\n\n\n\n\n\n\n\n\nIn console\nFirst move back to the branch that will be the recipient of the changes. This is usually master, but it could be another branch. Then merge your working branch into master.\n\ngit checkout master # Go back to master (or to the branch you want to move your )\ngit merge this_fancy_new_branch\n\nThis page shows a more advanced example of branching and explains a bit what is happening behind the scenes.\n\n\nIn Github: submitting pull requests\nWhile it is totally possible to merge two branches locally, or without informing anybody, a merge may be discussed or investigated by several people before being integrated to the master branch. To help with the process, Github offers some discussion features around the merge: the pull request.\nA pull request (a “PR”) is a request to merge one branch into another (in other words, a request that your working branch be pulled into the “main” branch). A pull request typically involves multiple commits. A pull request usually begins a conversation and review process before it is accepted and the branch is merged. For example, you can read pull request discussions on dplyr’s github.\nYou can submit a pull request (PR) directly form the website (as illustrated bellow) or from Github Desktop.\n\nGo to Github repository (online)\nView the tab “Pull Requests” and click the “New pull request” button\nSelect from the drop-down menu to merge your branch into main\nWrite a detailed Pull Request comment and click “Create Pull Request”.\n\nIn the image below, the branch “forests” has been selected to be merged into “main”:\n\n\n\n\n\n\n\n\n\nNow you should be able to see the pull request (example image below):\n\nReview the tab “Files changed” to see how the “main” branch would change if the branch were merged.\n\nOn the right, you can request a review from members of your team by tagging their Github ID. If you like, you can set the repository settings to require one approving review in order to merge into main.\n\nOnce the pull request is approved, a button to “Merge pull request” will become active. Click this.\n\nOnce completed, delete your branch as explained below.\n\n\n\n\n\n\n\n\n\n\n\n\nResolving conflicts\nWhen two people modified the same line(s) at the same time, a merge conflict arises. Indeed, Git refuses to make a decision about which version to keep, but it helps you find where the conflict is. DO NOT PANIC. Most of the time, it is pretty straightforward to resolve.\nFor example, on Github:\n\n\n\n\n\n\n\n\n\nAfter the merge raised a conflict, open the file in your favorite editor. The conflict will be indicated by series of characters:\n\n\n\n\n\n\n\n\n\nThe text between <<<<<<< HEAD and ======= comes from your local repository, and the one between ======= and >>>>>>> from the the other branch (which may be origin, master or any branch of your choice).\nYou need to decide which version of the code you prefer (or even write a third, including changes from both sides if pertinent), delete the rest and remove all the marks that Git added (<<<<<<< HEAD, =======, >>>>>>> origin/master/your_branch_name).\nThen, save the file, stage it and commit it : this is the commit that makes the merged version “official”. Do not forget to push afterwards.\nThe more often you and your collaborators pull and push, the smaller the conflicts will be.\nNote: If you feel at ease with the console, there are more advanced merging options (e.g. ignoring whitespace, giving a collaborator priority etc.).\n\n\nDelete your branch\nOnce a branch was merged into master and is no longer needed, you can delete it.\n\n46.10.0.1 Github + Rstudio\nGo to the repository on Github and click the button to view all the branches (next to the drop-down to select branches). Now find your branch and click the trash icon next to it. Read more detail on deleting a branch here.\nBe sure to also delete the branch locally on your computer. This will not happen automatically.\n\nFrom RStudio, make sure you are in the Main branch\nSwitch to typing Git commands in the RStudio “Terminal” (the tab adjacent to the R console), and type: git branch -d branch_name, where “branch_name” is the name of your branch to be deleted\nRefresh your Git tab and the branch should be gone\n\n\n\n46.10.0.2 In Github Desktop\nJust checkout the branch you want to delete, and go to the menu Branch > Delete.\n\n\n\nForking\nYou can fork a project if you would like to contribute to it but do not have the rights to do so, or if you just want to modify it for your personal use. A short description of forking can be found here.\nOn Github, click on the “Fork” button:\n\n\n\n\n\n\n\n\n\nThis will clone the original repository, but in your own profile. So now, there are two versions of the repository on Github: the original one, that you cannot modify, and the cloned version in your profile.\nThen, you can proceed to clone your version of the online repository locally on your computer, using any of the methods described in previous sections. Then, you can create a new branch, make changes, commit and push them to your remote repository.\nOnce you are happy with the result you can create a Pull Request from Github or Github Desktop to begin the conversation with the owners/maintainers of the original repository.\nWhat if you need some newer commits from the official repository?\nImagine that someone makes a critical modification to the official repository, which you want to include to your cloned version. It is possible to synchronize your fork with the official repository. It involves using the terminal, but it is not too complicated. You mostly need to remember that: - upstream = the official repository, the one that you could not modify - origin = your version of the repository on your Github profile\nYou can read this tutorial or follow along below:\nFirst, type in your Git terminal (inside your repo):\n\ngit remote -v\n\nIf you have not yet configured the upstream repository you should see two lines, beginning by origin. They show the remote repo that fetch and push point to. Remember, origin is the conventional nickname for your own version of the repository on Github. For example:\n\n\n\n\n\n\n\n\n\nNow, add a new remote repository:\n\ngit remote add upstream https://github.com/appliedepi/epirhandbook_eng.git\n\nHere the address is the address that Github generates when you clone a repository (see section on cloning). Now you will have four remote pointers:\n\n\n\n\n\n\n\n\n\nNow that the setup is done, whenever you want to get the changes from the original (upstream) repository, you just have to go (checkout) to the branch you want to update and type:\n\ngit fetch upstream # Get the new commits from the remote repository\ngit checkout the_branch_you_want_to_update\ngit merge upstream/the_branch_you_want_to_update # Merge the upstream branch into your branch.\ngit push # Update your own version of the remote repo\n\nIf there are conflicts, you will have to solve them, as explained in the Resolving conflicts section.\nSummary: forking is cloning, but on the Github server side. The rest of the actions are typical collaboration workflow actions (clone, push, pull, commit, merge, submit pull requests…).\nNote: while forking is a concept, not a Git command, it also exist on other Web hosts, like Bitbucket.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.10 Merge branch into Main", + "text": "47.10 Merge branch into Main\nIf you have finished making changes, you can begin the process of merging those changes into the main branch. Depending on your situation, this may be fast, or you may have deliberate review and approval steps involving teammates.\n\nLocally in Github Desktop\nOne can merge branches locally using Github Desktop. First, go to (checkout) the branch that will be the recipient of the commits, in other words, the branch you want to update. Then go to the menu Branch > Merge into current branch and click. A box will allow you to select the branch you want to import from.\n\n\n\n\n\n\n\n\n\n\n\nIn console\nFirst move back to the branch that will be the recipient of the changes. This is usually master, but it could be another branch. Then merge your working branch into master.\n\ngit checkout master # Go back to master (or to the branch you want to move your )\ngit merge this_fancy_new_branch\n\nThis page shows a more advanced example of branching and explains a bit what is happening behind the scenes.\n\n\nIn Github: submitting pull requests\nWhile it is totally possible to merge two branches locally, or without informing anybody, a merge may be discussed or investigated by several people before being integrated to the master branch. To help with the process, Github offers some discussion features around the merge: the pull request.\nA pull request (a “PR”) is a request to merge one branch into another (in other words, a request that your working branch be pulled into the “main” branch). A pull request typically involves multiple commits. A pull request usually begins a conversation and review process before it is accepted and the branch is merged. For example, you can read pull request discussions on dplyr’s github.\nYou can submit a pull request (PR) directly form the website (as illustrated bellow) or from Github Desktop.\n\nGo to Github repository (online).\nView the tab “Pull Requests” and click the “New pull request” button.\nSelect from the drop-down menu to merge your branch into main.\nWrite a detailed Pull Request comment and click “Create Pull Request”.\n\nIn the image below, the branch “forests” has been selected to be merged into “main”:\n\n\n\n\n\n\n\n\n\nNow you should be able to see the pull request (example image below):\n\nReview the tab “Files changed” to see how the “main” branch would change if the branch were merged.\n\nOn the right, you can request a review from members of your team by tagging their Github ID. If you like, you can set the repository settings to require one approving review in order to merge into main.\n\nOnce the pull request is approved, a button to “Merge pull request” will become active. Click this.\n\nOnce completed, delete your branch as explained below.\n\n\n\n\n\n\n\n\n\n\n\n\nResolving conflicts\nWhen two people modified the same line(s) at the same time, a merge conflict arises. Indeed, Git refuses to make a decision about which version to keep, but it helps you find where the conflict is. DO NOT PANIC. Most of the time, it is pretty straightforward to resolve.\nFor example, on Github:\n\n\n\n\n\n\n\n\n\nAfter the merge raised a conflict, open the file in your favorite editor. The conflict will be indicated by series of characters:\n\n\n\n\n\n\n\n\n\nThe text between <<<<<<< HEAD and ======= comes from your local repository, and the one between ======= and >>>>>>> from the the other branch (which may be origin, master or any branch of your choice).\nYou need to decide which version of the code you prefer (or even write a third, including changes from both sides if pertinent), delete the rest and remove all the marks that Git added (<<<<<<< HEAD, =======, >>>>>>> origin/master/your_branch_name).\nThen, save the file, stage it and commit it : this is the commit that makes the merged version “official”. Do not forget to push afterwards.\nThe more often you and your collaborators pull and push, the smaller the conflicts will be.\nNote: If you feel at ease with the console, there are more advanced merging options (e.g. ignoring whitespace, giving a collaborator priority etc.).\n\n\nDelete your branch\nOnce a branch was merged into master and is no longer needed, you can delete it.\n\n47.10.0.1 Github + Rstudio\nGo to the repository on Github and click the button to view all the branches (next to the drop-down to select branches). Now find your branch and click the trash icon next to it. Read more detail on deleting a branch here.\nBe sure to also delete the branch locally on your computer. This will not happen automatically.\n\nFrom RStudio, make sure you are in the Main branch.\nSwitch to typing Git commands in the RStudio “Terminal” (the tab adjacent to the R console), and type: git branch -d branch_name, where “branch_name” is the name of your branch to be deleted.\nRefresh your Git tab and the branch should be gone.\n\n\n\n47.10.0.2 In Github Desktop\nJust checkout the branch you want to delete, and go to the menu Branch > Delete.\n\n\n\nForking\nYou can fork a project if you would like to contribute to it but do not have the rights to do so, or if you just want to modify it for your personal use. A short description of forking can be found here.\nOn Github, click on the “Fork” button:\n\n\n\n\n\n\n\n\n\nThis will clone the original repository, but in your own profile. So now, there are two versions of the repository on Github: the original one, that you cannot modify, and the cloned version in your profile.\nThen, you can proceed to clone your version of the online repository locally on your computer, using any of the methods described in previous sections. Then, you can create a new branch, make changes, commit and push them to your remote repository.\nOnce you are happy with the result you can create a Pull Request from Github or Github Desktop to begin the conversation with the owners/maintainers of the original repository.\nWhat if you need some newer commits from the official repository?\nImagine that someone makes a critical modification to the official repository, which you want to include to your cloned version. It is possible to synchronize your fork with the official repository. It involves using the terminal, but it is not too complicated. You mostly need to remember that: - upstream = the official repository, the one that you could not modify - origin = your version of the repository on your Github profile\nYou can read this tutorial or follow along below:\nFirst, type in your Git terminal (inside your repo):\n\ngit remote -v\n\nIf you have not yet configured the upstream repository you should see two lines, beginning by origin. They show the remote repo that fetch and push point to. Remember, origin is the conventional nickname for your own version of the repository on Github. For example:\n\n\n\n\n\n\n\n\n\nNow, add a new remote repository:\n\ngit remote add upstream https://github.com/appliedepi/epirhandbook_eng.git\n\nHere the address is the address that Github generates when you clone a repository (see section on cloning). Now you will have four remote pointers:\n\n\n\n\n\n\n\n\n\nNow that the setup is done, whenever you want to get the changes from the original (upstream) repository, you just have to go (checkout) to the branch you want to update and type:\n\ngit fetch upstream # Get the new commits from the remote repository\ngit checkout the_branch_you_want_to_update\ngit merge upstream/the_branch_you_want_to_update # Merge the upstream branch into your branch.\ngit push # Update your own version of the remote repo\n\nIf there are conflicts, you will have to solve them, as explained in the Resolving conflicts section.\nSummary: forking is cloning, but on the Github server side. The rest of the actions are typical collaboration workflow actions (clone, push, pull, commit, merge, submit pull requests…).\nNote: while forking is a concept, not a Git command, it also exist on other Web hosts, like Bitbucket.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#what-we-learned", "href": "new_pages/collaboration.html#what-we-learned", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.11 What we learned", - "text": "46.11 What we learned\nYou have learned how to:\n\nsetup Git to track modifications in your folders,\n\nconnect your local repository to a remote online repository,\n\ncommit changes,\n\nsynchronize your local and remote repositories.\n\nAll this should get you going and be enough for most of your needs as epidemiologists. We usually do not have as advanced usage as developers.\nHowever, know that should you want (or need) to go further, Git offers more power to simplify commit histories, revert one or several commits, cherry-pick commits, etc. Some of it may sound like pure wizardry, but now that you have the basics, it is easier to build on it.\nNote that while the Git pane in Rstudio and Github Desktop are good for beginners / day-to-day usage in our line of work, they do not offer an interface to some of the intermediate / advanced Git functions. Some more complete interfaces allows you to do more with point-and-click (usually at the cost of a more complex layout).\nRemember that since you can use any tool at any point to track your repository, you can very easily install an interface to try it out sometimes, or to perform some less common complex task occasionally, while preferring a simplified interface for the rest of time (e.g. using Github Desktop most of the time, and switching to SourceTree or Gitbash for some specific tasks).", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.11 What we learned", + "text": "47.11 What we learned\nYou have learned how to:\n\nsetup Git to track modifications in your folders,\n\nconnect your local repository to a remote online repository,\n\ncommit changes,\n\nsynchronize your local and remote repositories.\n\nAll this should get you going and be enough for most of your needs as epidemiologists. We usually do not have as advanced usage as developers.\nHowever, know that should you want (or need) to go further, Git offers more power to simplify commit histories, revert one or several commits, cherry-pick commits, etc. Some of it may sound like pure wizardry, but now that you have the basics, it is easier to build on it.\nNote that while the Git pane in Rstudio and Github Desktop are good for beginners / day-to-day usage in our line of work, they do not offer an interface to some of the intermediate / advanced Git functions. Some more complete interfaces allows you to do more with point-and-click (usually at the cost of a more complex layout).\nRemember that since you can use any tool at any point to track your repository, you can very easily install an interface to try it out sometimes, or to perform some less common complex task occasionally, while preferring a simplified interface for the rest of time (e.g. using Github Desktop most of the time, and switching to SourceTree or Gitbash for some specific tasks).", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#git", "href": "new_pages/collaboration.html#git", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.12 Git commands", - "text": "46.12 Git commands\n\nRecommended learning\nTo learn Git commands in an interactive tutorial, see this website.\n\n\nWhere to enter commands\nYou enter commands in a Git shell.\nOption 1 You can open a new Terminal in RStudio. This tab is next to the R Console. If you cannot type any text in it, click on the drop-down menu below “Terminal” and select “New terminal”. Type the commands at the blinking space in front of the dollar sign “$”.\n\n\n\n\n\n\n\n\n\nOption 2 You can also open a shell (a terminal to enter commands) by clicking the blue “gears” icon in the Git tab (near the RStudio Environment). Select “Shell” from the drop-down menu. A new window will open where you can type the commands after the dollar sign “$”.\nOption 3 Right click to open “Git Bash here” which will open the same sort of terminal, or open Git Bash form your application list. More beginner-friendly informations on Git Bash, how to find it and some bash commands you will need.\n\n\nSample commands\nBelow we present a few common git commands. When you use them, keep in mind which branch is active (checked-out), as that will change the action!\nIn the commands below, represents a branch name. represents the hash ID of a specific commit. represents a number. Do not type the < or > symbols.\n\n\n\n\n\n\n\nGit command\nAction\n\n\n\n\ngit branch <name>\nCreate a new branch with the name \n\n\ngit checkout <name>\nSwitch current branch to \n\n\ngit checkout -b <name>\nShortcut to create new branch and switch to it\n\n\ngit status\nSee untracked changes\n\n\ngit add <file>\nStage a file\n\n\ngit commit -m <message>\nCommit currently staged changes to current branch with message\n\n\ngit fetch\nFetch commits from remote repository\n\n\ngit pull\nPull commits from remote repository in current branch\n\n\ngit push\nPush local commits to remote directory\n\n\ngit switch\nAn alternative to git checkout that is being phased in to Git\n\n\ngit merge <name>\nMerge branch into current branch\n\n\ngit rebase <name>\nAppend commits from current branch on to branch", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.12 Git commands", + "text": "47.12 Git commands\n\nRecommended learning\nTo learn Git commands in an interactive tutorial, see this website.\n\n\nWhere to enter commands\nYou enter commands in a Git shell.\nOption 1: You can open a new Terminal in RStudio. This tab is next to the R Console. If you cannot type any text in it, click on the drop-down menu below “Terminal” and select “New terminal”. Type the commands at the blinking space in front of the dollar sign “$”.\n\n\n\n\n\n\n\n\n\nOption 2: You can also open a shell (a terminal to enter commands) by clicking the blue “gears” icon in the Git tab (near the RStudio Environment). Select “Shell” from the drop-down menu. A new window will open where you can type the commands after the dollar sign “$”.\nOption 3: Right click to open “Git Bash here” which will open the same sort of terminal, or open Git Bash form your application list. More beginner-friendly informations on Git Bash, how to find it and some bash commands you will need.\n\n\nSample commands\nBelow we present a few common git commands. When you use them, keep in mind which branch is active (checked-out), as that will change the action!\nIn the commands below, represents a branch name. represents the hash ID of a specific commit. represents a number. Do not type the < or > symbols.\n\n\n\n\n\n\n\nGit command\nAction\n\n\n\n\ngit branch <name>\nCreate a new branch with the name \n\n\ngit checkout <name>\nSwitch current branch to \n\n\ngit checkout -b <name>\nShortcut to create new branch and switch to it\n\n\ngit status\nSee untracked changes\n\n\ngit add <file>\nStage a file\n\n\ngit commit -m <message>\nCommit currently staged changes to current branch with message\n\n\ngit fetch\nFetch commits from remote repository\n\n\ngit pull\nPull commits from remote repository in current branch\n\n\ngit push\nPush local commits to remote directory\n\n\ngit switch\nAn alternative to git checkout that is being phased in to Git\n\n\ngit merge <name>\nMerge branch into current branch\n\n\ngit rebase <name>\nAppend commits from current branch on to branch", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/collaboration.html#resources", "href": "new_pages/collaboration.html#resources", - "title": "46  Version control and collaboration with Git and Github", - "section": "46.13 Resources", - "text": "46.13 Resources\nMuch of this page was informed by this “Happy Git with R” website by Jenny Bryan. There is a very helpful section of this website that helps you troubleshoot common Git and R-related errors.\nThe Github.com documentation and start guide.\nThe RStudio “IDE” cheatsheet which includes tips on Git with RStudio.\nhttps://ohi-science.org/news/github-going-back-in-time\nGit commands for beginners\nAn interactive tutorial to learn Git commands.\nhttps://www.freecodecamp.org/news/an-introduction-to-git-for-absolute-beginners-86fa1d32ff71/: good for learning the absolute basics to track changes in one folder on you own computer.\nNice schematics to understand branches: https://speakerdeck.com/alicebartlett/git-for-humans\nTutorials covering both basic and more advanced subjects\nhttps://tutorialzine.com/2016/06/learn-git-in-30-minutes\nhttps://dzone.com/articles/git-tutorial-commands-and-operations-in-git https://swcarpentry.github.io/git-novice/ (short course) https://rsjakob.gitbooks.io/git/content/chapter1.html\nThe Pro Git book is considered an official reference. While some chapters are ok, it is usually a bit technical. It is probably a good resource once you have used Git a bit and want to learn a bit more precisely what happens and how to go further.", + "title": "47  Version control and collaboration with Git and Github", + "section": "47.13 Resources", + "text": "47.13 Resources\nMuch of this page was informed by this “Happy Git with R” website by Jenny Bryan. There is a very helpful section of this website that helps you troubleshoot common Git and R-related errors.\nThe Github.com documentation and start guide.\nThe RStudio “IDE” cheatsheet which includes tips on Git with RStudio.\nGitHub: A beginner’s guide to going back in time (aka fixing mistakes)\nGit commands for beginners\nAn interactive tutorial to learn Git commands.\nGit for Absolute Beginners good for learning the absolute basics to track changes in one folder on you own computer.\nNice schematics to understand branches\nTutorials covering both basic and more advanced subjects\nLearn Git in 30 Minutes\nCommands and Operations in Git\nVersion Control with Git (short course)\nIntroduction to Git (book)\nThe Pro Git book is considered an official reference. While some chapters are ok, it is usually a bit technical. It is probably a good resource once you have used Git a bit and want to learn a bit more precisely what happens and how to go further.", "crumbs": [ "Miscellaneous", - "46  Version control and collaboration with Git and Github" + "47  Version control and collaboration with Git and Github" ] }, { "objectID": "new_pages/errors.html", "href": "new_pages/errors.html", - "title": "47  Common errors", + "title": "48  Common errors", "section": "", - "text": "47.1 Interpreting error messages\nR errors can be cryptic at times, so Google is your friend. Search the error message with “R” and look for recent posts in StackExchange.com, stackoverflow.com, community.rstudio.com, twitter (#rstats), and other forums used by programmers to filed questions and answers. Try to find recent posts that have solved similar problems.\nIf after much searching you cannot find an answer to your problem, consider creating a reproducible example (“reprex”) and posting the question yourself. See the page on Getting help for tips on how to create and post a reproducible example to forums.", + "text": "48.1 Interpreting error messages\nR errors can be cryptic at times, so Google is your friend. Search the error message with “R” and look for recent posts in StackExchange.com, stackoverflow.com, forum.posit.co, twitter (#rstats), and other forums used by programmers to filed questions and answers. Try to find recent posts that have solved similar problems.\nIf after much searching you cannot find an answer to your problem, consider creating a reproducible example (“reprex”) and posting the question yourself. See the page on Getting help for tips on how to create and post a reproducible example to forums.", "crumbs": [ "Miscellaneous", - "47  Common errors" + "48  Common errors" ] }, { "objectID": "new_pages/errors.html#common-errors", "href": "new_pages/errors.html#common-errors", - "title": "47  Common errors", - "section": "47.2 Common errors", - "text": "47.2 Common errors\nBelow, we list some common errors and potential explanations/solutions. Some of these are borrowed from Noam Ross who analyzed the most common forum posts on Stack Overflow about R error messages (see analysis here)\n\nTypo errors\nError: unexpected symbol in:\n\" geom_histogram(stat = \"identity\")+\n tidyquant::geom_ma(n=7, size = 2, color = \"red\" lty\"\nIf you see “unexpected symbol”, check for missing commas\n\n\nPackage errors\ncould not find function \"x\"...\nThis likely means that you typed the function name incorrectly, or forgot to install or load a package.\nError in select(data, var) : unused argument (var)\nYou think you are using dplyr::select() but the select() function has been masked by MASS::select() - specify dplyr:: or re-order your package loading so that dplyr is after all the others.\nOther common masking errors stem from: plyr::summarise() and stats::filter(). Consider using the conflicted package.\nError in install.packages : ERROR: failed to lock directory ‘C:\\Users\\Name\\Documents\\R\\win-library\\4.0’ for modifying\nTry removing ‘C:\\Users\\Name\\Documents\\R\\win-library\\4.0/00LOCK’\nIf you get an error saying you need to remove an “00LOCK” file, go to your “R” library in your computer directory (e.g. R/win-library/) and look for a folder called “00LOCK”. Delete this manually, and try installing the package again. A previous install process was probably interrupted, which led to this.\n\n\nObject errors\nNo such file or directory:\nIf you see an error like this when you try to export or import: Check the spelling of the file and filepath, and if the path contains slashes make sure they are forward / and not backward \\. Also make sure you used the correct file extension (e.g. .csv, .xlsx).\nobject 'x' not found \nThis means that an object you are referencing does not exist. Perhaps code above did not run properly?\nError in 'x': subscript out of bounds\nThis means you tried to access something (an element of a vector or a list) that was not there.\n\n\nFunction syntax errors\n# ran recode without re-stating the x variable in mutate(x = recode(x, OLD = NEW)\nError: Problem with `mutate()` input `hospital`.\nx argument \".x\" is missing, with no default\ni Input `hospital` is `recode(...)`.\nThis error above (argument .x is missing, with no default) is common in mutate() if you are supplying a function like recode() or replace_na() where it expects you to provide the column name as the first argument. This is easy to forget.\n\n\nLogic errors\nError in if\nThis likely means an if statement was applied to something that was not TRUE or FALSE.\n\n\nFactor errors\n#Tried to add a value (\"Missing\") to a factor (with replace_na operating on a factor)\nProblem with `mutate()` input `age_cat`.\ni invalid factor level, NA generated\ni Input `age_cat` is `replace_na(age_cat, \"Missing\")`.invalid factor level, NA generated\nIf you see this error about invalid factor levels, you likely have a column of class Factor (which contains pre-defined levels) and tried to add a new value to it. Convert it to class Character before adding a new value.\n\n\nPlotting errors\nError: Insufficient values in manual scale. 3 needed but only 2 provided. ggplot() scale_fill_manual() values = c(“orange”, “purple”) … insufficient for number of factor levels … consider whether NA is now a factor level…\nCan't add x object\nYou probably have an extra + at the end of a ggplot command that you need to delete.\n\n\nR Markdown errors\nIf the error message contains something like Error in options[[sprintf(\"fig.%s\", i)]], check that your knitr options at the top of each chunk correctly use the out.width = or out.height = and not fig.width= and fig.height=.\n\n\nMiscellaneous\nConsider whether you re-arranged piped dplyr verbs and didn’t replace a pipe in the middle, or didn’t remove a pipe from the end after re-arranging.", + "title": "48  Common errors", + "section": "48.2 Common errors", + "text": "48.2 Common errors\nBelow, we list some common errors and potential explanations/solutions. Some of these are borrowed from Noam Ross who analyzed the most common forum posts on Stack Overflow about R error messages (see analysis here).\n\nTypo errors\nError: unexpected symbol in:\n\" geom_histogram(stat = \"identity\")+\n tidyquant::geom_ma(n=7, size = 2, color = \"red\" lty\"\nIf you see “unexpected symbol”, check for missing commas.\n\n\nPackage errors\ncould not find function \"x\"...\nThis likely means that you typed the function name incorrectly, or forgot to install or load a package.\nError in select(data, var) : unused argument (var)\nYou think you are using dplyr::select() but the select() function has been masked by MASS::select() - specify dplyr:: or re-order your package loading so that dplyr is after all the others.\nOther common masking errors stem from: plyr::summarise() and stats::filter(). Consider using the conflicted package.\nError in install.packages : ERROR: failed to lock directory ‘C:\\Users\\Name\\Documents\\R\\win-library\\4.0’ for modifying\nTry removing ‘C:\\Users\\Name\\Documents\\R\\win-library\\4.0/00LOCK’\nIf you get an error saying you need to remove an “00LOCK” file, go to your “R” library in your computer directory (e.g. R/win-library/) and look for a folder called “00LOCK”. Delete this manually, and try installing the package again. A previous install process was probably interrupted, which led to this.\n\n\nObject errors\nNo such file or directory:\nIf you see an error like this when you try to export or import: Check the spelling of the file and filepath, and if the path contains slashes make sure they are forward / and not backward \\. Also make sure you used the correct file extension (e.g. .csv, .xlsx).\nobject 'x' not found \nThis means that an object you are referencing does not exist. Perhaps code above did not run properly?\nError in 'x': subscript out of bounds\nThis means you tried to access something (an element of a vector or a list) that was not there.\n\n\nFunction syntax errors\n# ran recode without re-stating the x variable in mutate(x = recode(x, OLD = NEW)\nError: Problem with `mutate()` input `hospital`.\nx argument \".x\" is missing, with no default\ni Input `hospital` is `recode(...)`.\nThis error above (argument .x is missing, with no default) is common in mutate() if you are supplying a function like recode() or replace_na() where it expects you to provide the column name as the first argument. This is easy to forget.\n\n\nLogic errors\nError in if\nThis likely means an if statement was applied to something that was not TRUE or FALSE.\n\n\nFactor errors\n#Tried to add a value (\"Missing\") to a factor (with replace_na operating on a factor)\nProblem with `mutate()` input `age_cat`.\ni invalid factor level, NA generated\ni Input `age_cat` is `replace_na(age_cat, \"Missing\")`.invalid factor level, NA generated\nIf you see this error about invalid factor levels, you likely have a column of class Factor (which contains pre-defined levels) and tried to add a new value to it. Convert it to class Character before adding a new value.\n\n\nPlotting errors\nError: Insufficient values in manual scale. 3 needed but only 2 provided.\nYou may have supplied too few values for the color scale, make sure you have the same number of colors as values in your plot. Consider checking whether NA is a factor level in your dataset.\nCan't add x object\nThis is likely a ggplot error from the use of scale_fill_manual(), where you have not provided enough colors for the number of unique values. If the column is class factor, consider whether NA is a factor level.\n\n\nR Markdown errors\nIf the error message contains something like Error in options[[sprintf(\"fig.%s\", i)]], check that your knitr options at the top of each chunk correctly use the out.width = or out.height = and not fig.width= and fig.height=.\n\n\nMiscellaneous\nConsider whether you re-arranged piped dplyr verbs and didn’t replace a pipe in the middle, or didn’t remove a pipe from the end after re-arranging.", "crumbs": [ "Miscellaneous", - "47  Common errors" + "48  Common errors" ] }, { "objectID": "new_pages/errors.html#resources", "href": "new_pages/errors.html#resources", - "title": "47  Common errors", - "section": "47.3 Resources", - "text": "47.3 Resources\nThis is another blog post that lists common R programming errors faced by beginners", + "title": "48  Common errors", + "section": "48.3 Resources", + "text": "48.3 Resources\nThis is another blog post that lists common R programming errors faced by beginners\nDebugging your code\nAdvanced debugging", "crumbs": [ "Miscellaneous", - "47  Common errors" + "48  Common errors" ] }, { "objectID": "new_pages/help.html", "href": "new_pages/help.html", - "title": "48  Getting help", + "title": "49  Getting help", "section": "", - "text": "48.1 Github issues\nMany R packages and projects have their code hosted on the website Github.com. You can communicate directly with authors via this website by posting an “Issue”.\nRead more about how to store your work on Github in the page Collaboration and Github.\nOn Github, each project is contained within a repository. Each repository contains code, data, outputs, help documentation, etc. There is also a vehicle to communicate with the authors called “Issues”.\nSee below the Github page for the incidence2 package (used to make epidemic curves). You can see the “Issues” tab highlighted in yellow. You can see that there are 5 open issues.\nOnce in the Issues tab, you can see the open issues. Review them to ensure your problem is not already being addressed. You can open a new issue by clicking the green button on the right. You will need a Github account to do this.\nIn your issue, follow the instructions below to provide a minimal, reproducible example. And please be courteous! Most people developing R packages and projects are doing so in their spare time (like this handbook!).\nTo read more advanced materials about handling issues in your own Github repository, check out the Github documentation on Issues.", + "text": "49.1 Github issues\nMany R packages and projects have their code hosted on the website Github.com. You can communicate directly with authors via this website by posting an “Issue”.\nRead more about how to store your work on Github in the page Collaboration and Github.\nOn Github, each project is contained within a repository. Each repository contains code, data, outputs, help documentation, etc. There is also a vehicle to communicate with the authors called “Issues”.\nSee below the Github page for the incidence2 package (used to make epidemic curves). You can see the “Issues” tab highlighted in yellow. You can see that there are 5 open issues.\nOnce in the Issues tab, you can see the open issues. Review them to ensure your problem is not already being addressed. You can open a new issue by clicking the green button on the right. You will need a Github account to do this.\nIn your issue, follow the instructions below to provide a minimal, reproducible example. And please be courteous! Most people developing R packages and projects are doing so in their spare time (like this handbook!).\nTo read more advanced materials about handling issues in your own Github repository, check out the Github documentation on Issues.", "crumbs": [ "Miscellaneous", - "48  Getting help" + "49  Getting help" ] }, { "objectID": "new_pages/help.html#reproducible-example", "href": "new_pages/help.html#reproducible-example", - "title": "48  Getting help", - "section": "48.2 Reproducible example", - "text": "48.2 Reproducible example\nProviding a reproducible example (“reprex”) is key to getting help when posting in a forum or in a Github issue. People want to help you, but you have to give them an example that they can work with on their own computer. The example should:\n\nDemonstrate the problem you encountered\n\nBe minimal, in that it includes only the data and code required to reproduce your problem\n\nBe reproducible, such that all objects (e.g. data), package calls (e.g. library() or p_load()) are included\n\nAlso, be sure you do not post any sensitive data with the reprex! You can create example data frames, or use one of the data frames built into R (enter data() to open a list of these datasets).\n\nThe reprex package\nThe reprex package can assist you with making a reproducible example:\n\nreprex is installed with tidyverse, so load either package\n\n\n# install/load tidyverse (which includes reprex)\npacman::p_load(tidyverse)\n\n\nBegin an R script that creates your problem, step-by-step, starting from loading packages and data.\n\n\n# load packages\npacman::p_load(\n tidyverse, # data mgmt and vizualization\n outbreaks) # example outbreak datasets\n\n# flu epidemic case linelist\noutbreak_raw <- outbreaks::fluH7N9_china_2013 # retrieve dataset from outbreaks package\n\n# Clean dataset\noutbreak <- outbreak_raw %>% \n mutate(across(contains(\"date\"), as.Date))\n\n# Plot epidemic\n\nggplot(data = outbreak)+\n geom_histogram(\n mapping = aes(x = date_of_onset),\n binwidth = 7\n )+\n scale_x_date(\n date_format = \"%d %m\"\n )\n\nCopy all the code to your clipboard, and run the following command:\n\nreprex::reprex()\n\nYou will see an HTML output appear in the RStudio Viewer pane. It will contain all your code and any warnings, errors, or plot outputs. This output is also copied to your clipboard, so you can post it directly into a Github issue or a forum post.\n\n\n\n\n\n\n\n\n\n\nIf you set session_info = TRUE the output of sessioninfo::session_info() with your R and R package versions will be included\n\nYou can provide a working directory to wd =\n\nYou can read more about the arguments and possible variations at the documentation or by entering ?reprex\n\nIn the example above, the ggplot() command did not run because the arguemnt date_format = is not correct - it should be date_labels =.\n\n\nMinimal data\nThe helpers need to be able to use your data - ideally they need to be able to create it with code.\nTo create a minumal dataset, consider anonymising and using only a subset of the observations.\nUNDER CONSTRUCTION - you can also use the function dput() to create minimal dataset.", + "title": "49  Getting help", + "section": "49.2 Reproducible example", + "text": "49.2 Reproducible example\nProviding a reproducible example (“reprex”) is key to getting help when posting in a forum or in a Github issue. People want to help you, but you have to give them an example that they can work with on their own computer. The example should:\n\nDemonstrate the problem you encountered\n\nBe minimal, in that it includes only the data and code required to reproduce your problem\nBe reproducible, such that all objects (e.g. data), package calls (e.g. library() or p_load()) are included\n\nAlso, be sure you do not post any sensitive data with the reprex! You can create example data frames, or use one of the data frames built into R (enter data() to open a list of these datasets).\n\nThe reprex package\nThe reprex package can assist you with making a reproducible example:\n\nreprex is installed with tidyverse, so load either package.\n\n\n# install/load tidyverse (which includes reprex)\npacman::p_load(tidyverse)\n\n\nBegin an R script that creates your problem, step-by-step, starting from loading packages and data.\n\n\n# load packages\npacman::p_load(\n tidyverse, # data mgmt and vizualization\n outbreaks # example outbreak datasets\n ) \n\n# flu epidemic case linelist\noutbreak_raw <- outbreaks::fluH7N9_china_2013 # retrieve dataset from outbreaks package\n\n# Clean dataset\noutbreak <- outbreak_raw %>% \n mutate(across(contains(\"date\"), as.Date))\n\n# Plot epidemic\n\nggplot(data = outbreak) +\n geom_histogram(\n mapping = aes(x = date_of_onset),\n binwidth = 7\n ) +\n scale_x_date(\n date_labels = \"%d %m\"\n )\n\nCopy all the code to your clipboard, and run the following command:\n\nreprex::reprex()\n\nYou will see an HTML output appear in the RStudio Viewer pane. It will contain all your code and any warnings, errors, or plot outputs. This output is also copied to your clipboard, so you can post it directly into a Github issue or a forum post.\n\n\n\n\n\n\n\n\n\n\nIf you set session_info = TRUE the output of sessioninfo::session_info() with your R and R package versions will be included\nYou can provide a working directory to wd =\nYou can read more about the arguments and possible variations at the documentation or by entering ?reprex\n\nIn the example above, the ggplot() command did not run because the argument date_format = is not correct - it should be date_labels =.\n\n\nMinimal data\nThe helpers need to be able to use your data - ideally they need to be able to create it with code. To create a minimal dataset, consider anonymising and using only a subset of the observations, or replicating the data using “dummy values”. These seek to replicate the style (same class of variables, same amount of data, etc), but do not contain any sensitive information.\nFor example, imagine we have a linelist of those infected with a novel pathogen.\n\n#Create dataset\nlinelist_data <- data.frame(\n first_name = c(\"John\", \"Jane\", \"Joe\", \"Tom\", \"Richard\", \"Harry\"),\n last_name = c(\"Doe\", \"Doe\", \"Bloggs\", \"Jerry\", \"Springer\", \"Potter\"),\n age_years = c(25, 34, 27, 89, 52, 47),\n location = as.factor(c(\"London\", \"Paris\", \"Berlin\", \"Tokyo\", \"Canberra\", \"Rio de Janeiro\")),\n outcome = c(\"Died\", \"Recovered\", NA, \"Recovered\", \"Recovered\", \"Died\"),\n symptoms = as.factor(c(T, F, F, T, T, F))\n)\n\nYou can imagine that this would be very sensitive information, and it would not be appropriate (or even legal!) to share this information. To anonymise the data you could think about removing columns that are not necessary for the analysis, but are identifying. For instance you could:\n\nReplace first_name and last_name with an id column, that uses the rownumber\nRecode the location column so that it is not apparent where they were located\nChange the outcome to a binary value, to avoid disclosing their condition\n\nWe will be using the inbuilt vector LETTERS. This is the alphabet in capital letters, and is automatically part of your environment when you open R.\n\nlinelist_data %>%\n mutate(id = row_number(),\n location = as.factor(LETTERS[as.numeric(as.factor(location))]),\n outcome = as.character(as.factor(outcome))) %>%\n select(id, age_years, location, outcome, symptoms)\n\n id age_years location outcome symptoms\n1 1 25 C Died TRUE\n2 2 34 D Recovered FALSE\n3 3 27 A <NA> FALSE\n4 4 89 F Recovered TRUE\n5 5 52 B Recovered TRUE\n6 6 47 E Died FALSE\n\n\nYou could also generate new data, where columns are the same class (class()). This would also produce a reproducible dataset that does not contain any sensitive information.\n\ndummy_dataset <- data.frame(\n first_name = LETTERS[1:6],\n last_name = unique(iris$Species),\n age_years = sample(1:100, 6, replace = T),\n location = sample(row.names(USArrests), 6, replace = T),\n outcome = sample(c(\"Died\", \"Recovered\"), 6, replace = T),\n symptoms = sample(c(T, F), 6, replace = T)\n )\n\ndummy_dataset\n\n first_name last_name age_years location outcome symptoms\n1 A setosa 84 Virginia Died FALSE\n2 B versicolor 36 Tennessee Recovered FALSE\n3 C virginica 9 Texas Recovered FALSE\n4 D setosa 33 South Dakota Died TRUE\n5 E versicolor 94 Montana Died TRUE\n6 F virginica 24 Illinois Died FALSE", "crumbs": [ "Miscellaneous", - "48  Getting help" + "49  Getting help" ] }, { "objectID": "new_pages/help.html#posting-to-a-forum", "href": "new_pages/help.html#posting-to-a-forum", - "title": "48  Getting help", - "section": "48.3 Posting to a forum", - "text": "48.3 Posting to a forum\nRead lots of forum posts. Get an understanding for which posts are well-written, and which ones are not.\n\nFirst, decide whether to ask the question at all. Have you thoroughly reviewed the forum website, trying various search terms, to see if your question has already been asked?\nGive your question an informative title (not “Help! this isn’t working”).\nWrite your question:\n\n\nIntroduce your situation and problem\n\nLink to posts of similar issues and explain how they do not answer your question\n\nInclude any relevant information to help someone who does not know the context of your work\n\nGive a minimal reproducible example with your R session information\n\nUse proper spelling, grammar, punctuation, and break your question into paragraphs so that it is easier to read\n\n\nMonitor your question once posted to respond to any requests for clarification. Be courteous and gracious - often the people answering are volunteering their time to help you. If you have a follow-up question consider whether it should be a separate posted question.\nMark the question as answered, if you get an answer that meets the original request. This helps others later quickly recognize the solution.\n\nRead these posts about how to ask a good question the Stack overflow code of conduct.", + "title": "49  Getting help", + "section": "49.3 Posting to a forum", + "text": "49.3 Posting to a forum\nOne highly recommend forum for posting your questions on is the Applied Epi Community Forum. There are a wealth of topics, and experts, to be found on our forum, ready to help you with questions on Epi methods, R code, and many other topics. Note, you will have to make an account in order to post a question!\nIn order to make it as easy as possible for others to understand and help you, you should approach posting your question as follows.\n\nFirst, decide whether to ask the question at all. Have you thoroughly reviewed the forum website, trying various search terms, to see if your question has already been asked?\nGive your question an informative title (not “Help! this isn’t working”).\nWrite your question:\n\n\nIntroduce your situation and problem\nLink to posts of similar issues and explain how they do not answer your question\n\nInclude any relevant information to help someone who does not know the context of your work\nGive a minimal reproducible example with your R session information\nUse proper spelling, grammar, punctuation, and break your question into paragraphs so that it is easier to read\n\n\nMonitor your question once posted to respond to any requests for clarification. Be courteous and gracious - often the people answering are volunteering their time to help you. If you have a follow-up question consider whether it should be a separate posted question\nMark the question as answered, if you get an answer that meets the original request. This helps others later quickly recognize the solution\n\nRead these posts about how to ask a good question the Stack overflow code of conduct, and the Applied Epi Community post on “How to make a reproducible R code example”.", "crumbs": [ "Miscellaneous", - "48  Getting help" + "49  Getting help" ] }, { "objectID": "new_pages/help.html#resources", "href": "new_pages/help.html#resources", - "title": "48  Getting help", - "section": "48.4 Resources", - "text": "48.4 Resources\nTidyverse page on how to get help!\nTips on producing a minimal dataset\nDocumentation for the dput function", + "title": "49  Getting help", + "section": "49.4 Resources", + "text": "49.4 Resources\nTidyverse page on how to get help!\nTips on producing a minimal dataset\nThe Applied Epi Community forum\nApplied Epi Community post on “How to make a reproducible R code example”.", "crumbs": [ "Miscellaneous", - "48  Getting help" + "49  Getting help" ] }, { @@ -4454,7 +4454,7 @@ "href": "new_pages/network_drives.html#troubleshooting-common-errors", "title": "49  R on network drives", "section": "49.4 Troubleshooting common errors", - "text": "49.4 Troubleshooting common errors\n“Failed to compile…tex in rmarkdown”\n\nCheck the installation of TinyTex, or install TinyTex to C: location. See the R basics page on how to install TinyTex.\n\n\n# check/install tinytex, to C: location\ntinytex::install_tinytex()\ntinytex:::is_tinytex() # should return TRUE (note three colons)\n\nInternet routines cannot be loaded\nFor example, Error in tools::startDynamicHelp() : internet routines cannot be loaded\n\nTry selecting 32-bit version from RStudio via Tools/Global Options.\n\nnote: if 32-bit version does not appear in menu, make sure you are not using RStudio v1.2.\n\n\nAlternatively, try uninstalling R and re-installing with different bit version (32 instead of 64)\n\nC: library does not appear as an option when I try to install packages manually\n\nRun RStudio as an administrator, then this option will appear.\n\nTo set-up RStudio to always run as administrator (advantageous when using an Rproject where you don’t click RStudio icon to open)… right-click the Rstudio icon\n\nThe image below shows how you can manually select the library to install a package to. This window appears when you open the Packages RStudio pane and click “Install”.\n\n\n\n\n\n\n\n\n\nPandoc 1 error\nIf you are getting “pandoc error 1” when knitting R Markdowns scripts on network drives:\n\nOf multiple library locations, have the one with a lettered drive listed first (see codes above)\n\nThe above solution worked when knitting on local drive but while on a networked internet connection\n\nSee more tips here: https://ciser.cornell.edu/rmarkdown-knit-to-html-word-pdf/\n\nPandoc Error 83\nThe error will look something like this: can't find file...rmarkdown...lua.... This means that it was unable to find this file.\nSee https://stackoverflow.com/questions/58830927/rmarkdown-unable-to-locate-lua-filter-when-knitting-to-word\nPossibilities:\n\nRmarkdown package is not installed\n\nRmarkdown package is not findable\n\nAn admin rights issue.\n\nIt is possible that R is not able to find the rmarkdown package file, so check which library the rmarkdown package lives (see code above). If the package is installed to a library that in inaccessible (e.g. starts with “\\\") consider manually moving it to C: or other named drive library. Be aware that the rmarkdown package has to be able to connect to TinyTex installation, so can not live in a library on a network drive.\nPandoc Error 61\nFor example: Error: pandoc document conversion failed with error 61 or Could not fetch...\n\nTry running RStudio as administrator (right click icon, select run as admin, see above instructions)\n\nAlso see if the specific package that was unable to be reached can be moved to C: library.\n\nLaTex error (see below)\nAn error like: ! Package pdftex.def Error: File 'cict_qm2_2020-06-29_files/figure-latex/unnamed-chunk-5-1.png' not found: using draft setting. or Error: LaTeX failed to compile file_name.tex.\n\nSee https://yihui.org/tinytex/r/#debugging for debugging tips.\n\nSee file_name.log for more info.\n\nPandoc Error 127\nThis could be a RAM (space) issue. Re-start your R session and try again.\nMapping network drives\nMapping a network drive can be risky. Consult with your IT department before attempting this.\nA tip borrowed from this forum discussion:\nHow does one open a file “through a mapped network drive”?\n\nFirst, you’ll need to know the network location you’re trying to access.\n\nNext, in the Windows file manager, you will need to right click on “This PC” on the right hand pane, and select “Map a network drive”.\n\nGo through the dialogue to define the network location from earlier as a lettered drive.\n\nNow you have two ways to get to the file you’re opening. Using the drive-letter path should work.\n\nError in install.packages()\nIf you get an error that includes mention of a “lock” directory, for example: Error in install.packages : ERROR: failed to lock directory...\nLook in your package library and you will see a folder whose name begins with “00LOCK”. Try the following tips:\n\nManually delete the “00LOCK” folder directory from your package library. Try installing the package again.\n\nYou can also try the command pacman::p_unlock() (you can also put this command in the Rprofile so it runs every time project opens.). Then try installing the package again. It may take several tries.\n\nTry running RStudio in Administrator mode, and try installing the packages one-by-one.\n\nIf all else fails, install the package to another library or folder (e.g. Temp) and then manually copy the package’s folder over to the desired library.", + "text": "49.4 Troubleshooting common errors\n“Failed to compile…tex in rmarkdown”\n\nCheck the installation of TinyTex, or install TinyTex to C: location. See the R basics page on how to install TinyTex.\n\n\n# check/install tinytex, to C: location\ntinytex::install_tinytex()\ntinytex:::is_tinytex() # should return TRUE (note three colons)\n\nInternet routines cannot be loaded\nFor example, Error in tools::startDynamicHelp() : internet routines cannot be loaded\n\nTry selecting 32-bit version from RStudio via Tools/Global Options.\n\nnote: if 32-bit version does not appear in menu, make sure you are not using RStudio v1.2.\n\n\nAlternatively, try uninstalling R and re-installing with different bit version (32 instead of 64).\n\nC: library does not appear as an option when I try to install packages manually\n\nRun RStudio as an administrator, then this option will appear.\n\nTo set-up RStudio to always run as administrator (advantageous when using an Rproject where you don’t click RStudio icon to open), right-click the Rstudio icon.\n\nThe image below shows how you can manually select the library to install a package to. This window appears when you open the Packages RStudio pane and click “Install”.\n\n\n\n\n\n\n\n\n\nPandoc 1 error\nIf you are getting “pandoc error 1” when knitting R Markdowns scripts on network drives:\n\nOf multiple library locations, have the one with a lettered drive listed first (see codes above).\n\nThe above solution worked when knitting on local drive but while on a networked internet connection.\n\nSee more tips here.\n\nPandoc Error 83\nThe error will look something like this: can't find file...rmarkdown...lua.... This means that it was unable to find this file.\nSee here.\nPossibilities:\n\nRmarkdown package is not installed.\n\nRmarkdown package is not findable.\n\nAn admin rights issue.\n\nIt is possible that R is not able to find the rmarkdown package file, so check which library the rmarkdown package lives (see code above). If the package is installed to a library that in inaccessible (e.g. starts with “\\\") consider manually moving it to C: or other named drive library. Be aware that the rmarkdown package has to be able to connect to TinyTex installation, so can not live in a library on a network drive.\nPandoc Error 61\nFor example: Error: pandoc document conversion failed with error 61 or Could not fetch...\n\nTry running RStudio as administrator (right click icon, select run as admin, see above instructions).\n\nAlso see if the specific package that was unable to be reached can be moved to C: library.\n\nLaTex error (see below)\nAn error like: ! Package pdftex.def Error: File 'cict_qm2_2020-06-29_files/figure-latex/unnamed-chunk-5-1.png' not found: using draft setting. or Error: LaTeX failed to compile file_name.tex.\n\nSee this article for debugging tips.\n\nSee file_name.log for more info.\n\nPandoc Error 127\nThis could be a RAM (space) issue. Re-start your R session and try again.\nMapping network drives\nMapping a network drive can be risky. Consult with your IT department before attempting this.\nA tip borrowed from this forum discussion:\nHow does one open a file “through a mapped network drive”?\n\nFirst, you’ll need to know the network location you’re trying to access.\n\nNext, in the Windows file manager, you will need to right click on “This PC” on the right hand pane, and select “Map a network drive”.\n\nGo through the dialogue to define the network location from earlier as a lettered drive.\n\nNow you have two ways to get to the file you’re opening. Using the drive-letter path should work.\n\nError in install.packages()\nIf you get an error that includes mention of a “lock” directory, for example: Error in install.packages : ERROR: failed to lock directory...\nLook in your package library and you will see a folder whose name begins with “00LOCK”. Try the following tips:\n\nManually delete the “00LOCK” folder directory from your package library. Try installing the package again.\n\nYou can also try the command pacman::p_unlock() (you can also put this command in the Rprofile so it runs every time project opens.). Then try installing the package again. It may take several tries.\n\nTry running RStudio in Administrator mode, and try installing the packages one-by-one.\n\nIf all else fails, install the package to another library or folder (e.g. Temp) and then manually copy the package’s folder over to the desired library.", "crumbs": [ "Miscellaneous", "49  R on network drives" @@ -4463,89 +4463,199 @@ { "objectID": "new_pages/data_table.html", "href": "new_pages/data_table.html", - "title": "50  Data Table", + "title": "51  Data Table", "section": "", - "text": "50.1 Intro to data tables\nA data table is a 2-dimensional data structure like a data frame that allows complex grouping operations to be performed. The data.table syntax is structured so that operations can be performed on rows, columns and groups.\nThe structure is DT[i, j, by], separated by 3 parts; the i, j and by arguments. The i argument allows for subsetting of required rows, the j argument allows you to operate on columns and the by argument allows you operate on columns by groups.\nThis page will address the following topics:", + "text": "51.1 Intro to data tables\nA data table is a 2-dimensional data structure like a data frame that allows complex grouping operations to be performed. The data.table syntax is structured so that operations can be performed on rows, columns and groups.\nThe structure is DT[i, j, by], separated by 3 parts; the i, j and by arguments. The i argument allows for subsetting of required rows, the j argument allows you to operate on columns and the by argument allows you operate on columns by groups.\nThis page will address the following topics:", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#intro-to-data-tables", "href": "new_pages/data_table.html#intro-to-data-tables", - "title": "50  Data Table", + "title": "51  Data Table", "section": "", "text": "Importing data and use of fread() and fwrite()\nSelecting and filtering rows using the i argument\nUsing helper functions %like%, %chin%, %between%\nSelecting and computing on columns using the j argument\nComputing by groups using the by argument\nAdding and updating data to data tables using :=", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#load-packages-and-import-data", "href": "new_pages/data_table.html#load-packages-and-import-data", - "title": "50  Data Table", - "section": "50.2 Load packages and import data", - "text": "50.2 Load packages and import data\n\nLoad packages\nUsing the p_load() function from pacman, we load (and install if necessary) packages required for this analysis.\n\npacman::p_load(\n rio, # to import data\n data.table, # to group and clean data\n tidyverse, # allows use of pipe (%>%) function in this chapter\n here \n ) \n\n\n\nImport data\nThis page will explore some of the core functions of data.table using the case linelist referenced throughout the handbook.\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instructions in the Download book and data page. The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data. From here we use data.table() to convert the data frame to a data table.\n\nlinelist <- rio::import(here(\"data\", \"linelist_cleaned.xlsx\")) %>% data.table()\n\nThe fread() function is used to directly import regular delimited files, such as .csv files, directly to a data table format. This function, and its counterpart, fwrite(), used for writing data.tables as regular delimited files are very fast and computationally efficient options for large databases.\nThe first 20 rows of linelist:\nBase R commands such as dim() that are used for data frames can also be used for data tables\n\ndim(linelist) #gives the number of rows and columns in the data table\n\n[1] 5888 30", + "title": "51  Data Table", + "section": "51.2 Load packages and import data", + "text": "51.2 Load packages and import data\n\nLoad packages\nUsing the p_load() function from pacman, we load (and install if necessary) packages required for this analysis.\n\npacman::p_load(\n rio, # to import data\n data.table, # to group and clean data\n tidyverse, # allows use of pipe (%>%) function in this chapter\n here \n ) \n\n\n\nImport data\nThis page will explore some of the core functions of data.table using the case linelist referenced throughout the handbook.\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instructions in the Download book and data page. The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data. From here we use data.table() to convert the data frame to a data table.\n\nlinelist <- rio::import(here(\"data\", \"linelist_cleaned.xlsx\")) %>% \n data.table()\n\nThe fread() function is used to directly import regular delimited files, such as .csv files, directly to a data table format. This function, and its counterpart, fwrite(), used for writing data.tables as regular delimited files are very fast and computationally efficient options for large databases.\nThe first 20 rows of linelist:\nBase R commands such as dim() that are used for data frames can also be used for data tables\n\ndim(linelist) #gives the number of rows and columns in the data table\n\n[1] 5888 30", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#the-i-argument-selecting-and-filtering-rows", "href": "new_pages/data_table.html#the-i-argument-selecting-and-filtering-rows", - "title": "50  Data Table", - "section": "50.3 The i argument: selecting and filtering rows", - "text": "50.3 The i argument: selecting and filtering rows\nRecalling the DT[i, j, by] structure, we can filter rows using either row numbers or logical expressions. The i argument is first; therefore, the syntax DT[i] or DT[i,] can be used.\nThe first example retrieves the first 5 rows of the data table, the second example subsets cases are 18 years or over, and the third example subsets cases 18 years old or over but not diagnosed at the Central Hospital:\n\nlinelist[1:5] #returns the 1st to 5th row\nlinelist[age >= 18] #subsets cases are equal to or over 18 years\nlinelist[age >= 18 & hospital != \"Central Hospital\"] #subsets cases equal to or over 18 years old but not diagnosed at the Central Hospital\n\nUsing .N in the i argument represents the total number of rows in the data table. This can be used to subset on the row numbers:\n\nlinelist[.N] #returns the last row\nlinelist[15:.N] #returns the 15th to the last row\n\n\nUsing helper functions for filtering\nData table uses helper functions that make subsetting rows easy. The %like% function is used to match a pattern in a column, %chin% is used to match a specific character, and the %between% helper function is used to match numeric columns within a prespecified range.\nIn the following examples we: * filter rows where the hospital variable contains “Hospital” * filter rows where the outcome is “Recover” or “Death” * filter rows in the age range 40-60\n\nlinelist[hospital %like% \"Hospital\"] #filter rows where the hospital variable contains “Hospital”\nlinelist[outcome %chin% c(\"Recover\", \"Death\")] #filter rows where the outcome is “Recover” or “Death”\nlinelist[age %between% c(40, 60)] #filter rows in the age range 40-60\n\n#%between% must take a vector of length 2, whereas %chin% can take vectors of length >= 1", + "title": "51  Data Table", + "section": "51.3 The i argument: selecting and filtering rows", + "text": "51.3 The i argument: selecting and filtering rows\nRecalling the DT[i, j, by] structure, we can filter rows using either row numbers or logical expressions. The i argument is first; therefore, the syntax DT[i] or DT[i,] can be used.\nThe first example retrieves the first 5 rows of the data table, the second example subsets cases are 18 years or over, and the third example subsets cases 18 years old or over but not diagnosed at the Central Hospital:\n\nlinelist[1:5] #returns the 1st to 5th row\nlinelist[age >= 18] #subsets cases are equal to or over 18 years\nlinelist[age >= 18 & hospital != \"Central Hospital\"] #subsets cases equal to or over 18 years old but not diagnosed at the Central Hospital\n\nUsing .N in the i argument represents the total number of rows in the data table. This can be used to subset on the row numbers:\n\nlinelist[.N] #returns the last row\nlinelist[15:.N] #returns the 15th to the last row\n\n\nUsing helper functions for filtering\nData table uses helper functions that make subsetting rows easy. The %like% function is used to match a pattern in a column, %chin% is used to match a specific character, and the %between% helper function is used to match numeric columns within a specified range.\nIn the following examples we:\n\nFilter rows where the hospital variable contains “Hospital”\nFilter rows where the outcome is “Recover” or “Death”\nFilter rows in the age range 40-60\n\n\nlinelist[hospital %like% \"Hospital\"] #filter rows where the hospital variable contains “Hospital”\nlinelist[outcome %chin% c(\"Recover\", \"Death\")] #filter rows where the outcome is “Recover” or “Death”\nlinelist[age %between% c(40, 60)] #filter rows in the age range 40-60\n\n#%between% must take a vector of length 2, whereas %chin% can take vectors of length >= 1", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#the-j-argument-selecting-and-computing-on-columns", "href": "new_pages/data_table.html#the-j-argument-selecting-and-computing-on-columns", - "title": "50  Data Table", - "section": "50.4 The j argument: selecting and computing on columns", - "text": "50.4 The j argument: selecting and computing on columns\nUsing the DT[i, j, by] structure, we can select columns using numbers or names. The j argument is second; therefore, the syntax DT[, j] is used. To facilitate computations on the j argument, the column is wrapped using either list() or .().\n\nSelecting columns\nThe first example retrieves the first, third and fifth columns of the data table, the second example selects all columns except the height, weight and gender columns. The third example uses the .() wrap to select the case_id and outcome columns.\n\nlinelist[ , c(1,3,5)]\nlinelist[ , -c(\"gender\", \"age\", \"wt_kg\", \"ht_cm\")]\nlinelist[ , list(case_id, outcome)] #linelist[ , .(case_id, outcome)] works just as well\n\n\n\nComputing on columns\nBy combining the i and j arguments it is possible to filter rows and compute on the columns. Using .N in the j argument also represents the total number of rows in the data table and can be useful to return the number of rows after row filtering.\nIn the following examples we: * Count the number of cases that stayed over 7 days in hospital * Calculate the mean age of the cases that died at the military hospital * Calculate the standard deviation, median, mean age of the cases that recovered at the central hospital\n\nlinelist[days_onset_hosp > 7 , .N]\n\n[1] 189\n\nlinelist[hospital %like% \"Military\" & outcome %chin% \"Death\", .(mean(age, na.rm = T))] #na.rm = T removes N/A values\n\n V1\n <num>\n1: 15.9084\n\nlinelist[hospital == \"Central Hospital\" & outcome == \"Recover\", \n .(mean_age = mean(age, na.rm = T),\n median_age = median(age, na.rm = T),\n sd_age = sd(age, na.rm = T))] #this syntax does not use the helper functions but works just as well\n\n mean_age median_age sd_age\n <num> <num> <num>\n1: 16.85185 14 12.93857\n\n\nRemember using the .() wrap in the j argument facilitates computation, returns a data table and allows for column naming.", + "title": "51  Data Table", + "section": "51.4 The j argument: selecting and computing on columns", + "text": "51.4 The j argument: selecting and computing on columns\nUsing the DT[i, j, by] structure, we can select columns using numbers or names. The j argument is second; therefore, the syntax DT[, j] is used. To facilitate computations on the j argument, the column is wrapped using either list() or .().\n\nSelecting columns\nThe first example retrieves the first, third and fifth columns of the data table, the second example selects all columns except the height, weight and gender columns. The third example uses the .() wrap to select the case_id and outcome columns.\n\nlinelist[ , c(1,3,5)]\nlinelist[ , -c(\"gender\", \"age\", \"wt_kg\", \"ht_cm\")]\nlinelist[ , list(case_id, outcome)] #linelist[ , .(case_id, outcome)] works just as well\n\n\n\nComputing on columns\nBy combining the i and j arguments it is possible to filter rows and compute on the columns. Using .N in the j argument also represents the total number of rows in the data table and can be useful to return the number of rows after row filtering.\nIn the following examples we: * Count the number of cases that stayed over 7 days in hospital * Calculate the mean age of the cases that died at the military hospital * Calculate the standard deviation, median, mean age of the cases that recovered at the central hospital\n\nlinelist[days_onset_hosp > 7 , .N]\n\n[1] 189\n\nlinelist[hospital %like% \"Military\" & outcome %chin% \"Death\", .(mean(age, na.rm = T))] #na.rm = T removes N/A values\n\n V1\n <num>\n1: 15.9084\n\nlinelist[hospital == \"Central Hospital\" & outcome == \"Recover\", \n .(mean_age = mean(age, na.rm = T),\n median_age = median(age, na.rm = T),\n sd_age = sd(age, na.rm = T))] #this syntax does not use the helper functions but works just as well\n\n mean_age median_age sd_age\n <num> <num> <num>\n1: 16.85185 14 12.93857\n\n\nRemember using the .() wrap in the j argument facilitates computation, returns a data table and allows for column naming.", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#the-by-argument-computing-by-groups", "href": "new_pages/data_table.html#the-by-argument-computing-by-groups", - "title": "50  Data Table", - "section": "50.5 The by argument: computing by groups", - "text": "50.5 The by argument: computing by groups\nThe by argument is the third argument in the DT[i, j, by] structure. The by argument accepts both a character vector and the list() or .() syntax. Using the .() syntax in the by argument allows column renaming on the fly.\nIn the following examples we:\n* group the number of cases by hospital * in cases 18 years old or over, calculate the mean height and weight of cases according to gender and whether they recovered or died * in admissions that lasted over 7 days, count the number of cases according to the month they were admitted and the hospital they were admitted to\n\nlinelist[, .N, .(hospital)] #the number of cases by hospital\n\n hospital N\n <char> <int>\n1: Other 885\n2: Missing 1469\n3: St. Mark's Maternity Hospital (SMMH) 422\n4: Port Hospital 1762\n5: Military Hospital 896\n6: Central Hospital 454\n\nlinelist[age > 18, .(mean_wt = mean(wt_kg, na.rm = T),\n mean_ht = mean(ht_cm, na.rm = T)), .(gender, outcome)] #NAs represent the categories where the data is missing\n\n gender outcome mean_wt mean_ht\n <char> <char> <num> <num>\n1: m Recover 71.90227 178.1977\n2: f Death 63.27273 159.9448\n3: m Death 71.61770 175.4726\n4: f <NA> 64.49375 162.7875\n5: m <NA> 72.65505 176.9686\n6: f Recover 62.86498 159.2996\n7: <NA> Recover 67.21429 175.2143\n8: <NA> Death 69.16667 170.7917\n9: <NA> <NA> 70.25000 175.5000\n\nlinelist[days_onset_hosp > 7, .N, .(month = month(date_hospitalisation), hospital)]\n\n month hospital N\n <num> <char> <int>\n 1: 5 Military Hospital 3\n 2: 6 Port Hospital 4\n 3: 7 Port Hospital 8\n 4: 8 St. Mark's Maternity Hospital (SMMH) 5\n 5: 8 Military Hospital 9\n 6: 8 Other 10\n 7: 8 Port Hospital 10\n 8: 9 Port Hospital 28\n 9: 9 Missing 27\n10: 9 Central Hospital 10\n11: 9 St. Mark's Maternity Hospital (SMMH) 6\n12: 10 Missing 2\n13: 10 Military Hospital 3\n14: 3 Port Hospital 1\n15: 4 Military Hospital 1\n16: 5 Other 2\n17: 5 Central Hospital 1\n18: 5 Missing 1\n19: 6 Missing 7\n20: 6 St. Mark's Maternity Hospital (SMMH) 2\n21: 6 Military Hospital 1\n22: 7 Military Hospital 3\n23: 7 Other 1\n24: 7 Missing 2\n25: 7 St. Mark's Maternity Hospital (SMMH) 1\n26: 8 Central Hospital 2\n27: 8 Missing 6\n28: 9 Other 9\n29: 9 Military Hospital 11\n30: 10 Port Hospital 3\n31: 10 Other 4\n32: 10 St. Mark's Maternity Hospital (SMMH) 1\n33: 10 Central Hospital 1\n34: 11 Missing 2\n35: 11 Port Hospital 1\n36: 12 Port Hospital 1\n month hospital N\n\n\nData.table also allows the chaining expressions as follows:\n\nlinelist[, .N, .(hospital)][order(-N)][1:3] #1st selects all cases by hospital, 2nd orders the cases in descending order, 3rd subsets the 3 hospitals with the largest caseload\n\n hospital N\n <char> <int>\n1: Port Hospital 1762\n2: Missing 1469\n3: Military Hospital 896\n\n\nIn these examples we are following the assumption that a row in the data table is equal to a new case, and so we can use the .N to represent the number of rows in the data table. Another useful function to represent the number of unique cases is uniqueN(), which returns the number of unique values in a given input. This is illustrated here:\n\nlinelist[, .(uniqueN(gender))] #remember .() in the j argument returns a data table\n\n V1\n <int>\n1: 3\n\n\nThe answer is 3, as the unique values in the gender column are m, f and N/A. Compare with the base R function unique(), which returns all the unique values in a given input:\n\nlinelist[, .(unique(gender))]\n\n V1\n <char>\n1: m\n2: f\n3: <NA>\n\n\nTo find the number of unique cases in a given month we would write the following:\n\nlinelist[, .(uniqueN(case_id)), .(month = month(date_hospitalisation))]\n\n month V1\n <num> <int>\n 1: 5 62\n 2: 6 100\n 3: 7 198\n 4: 8 509\n 5: 9 1170\n 6: 10 1228\n 7: 11 813\n 8: 12 576\n 9: 1 434\n10: 2 310\n11: 3 290\n12: 4 198", + "title": "51  Data Table", + "section": "51.5 The by argument: computing by groups", + "text": "51.5 The by argument: computing by groups\nThe by argument is the third argument in the DT[i, j, by] structure. The by argument accepts both a character vector and the list() or .() syntax. Using the .() syntax in the by argument allows column renaming on the fly.\nIn the following examples we:\n\ngroup the number of cases by hospital\nin cases 18 years old or over, calculate the mean height and weight of cases according to gender and whether they recovered or died\nin admissions that lasted over 7 days, count the number of cases according to the month they were admitted and the hospital they were admitted to\n\n\nlinelist[, .N, .(hospital)] #the number of cases by hospital\n\n hospital N\n <char> <int>\n1: Other 885\n2: Missing 1469\n3: St. Mark's Maternity Hospital (SMMH) 422\n4: Port Hospital 1762\n5: Military Hospital 896\n6: Central Hospital 454\n\nlinelist[age > 18, .(mean_wt = mean(wt_kg, na.rm = T),\n mean_ht = mean(ht_cm, na.rm = T)), .(gender, outcome)] #NAs represent the categories where the data is missing\n\n gender outcome mean_wt mean_ht\n <char> <char> <num> <num>\n1: m Recover 71.90227 178.1977\n2: f Death 63.27273 159.9448\n3: m Death 71.61770 175.4726\n4: f <NA> 64.49375 162.7875\n5: m <NA> 72.65505 176.9686\n6: f Recover 62.86498 159.2996\n7: <NA> Recover 67.21429 175.2143\n8: <NA> Death 69.16667 170.7917\n9: <NA> <NA> 70.25000 175.5000\n\nlinelist[days_onset_hosp > 7, .N, .(month = month(date_hospitalisation), hospital)]\n\n month hospital N\n <num> <char> <int>\n 1: 5 Military Hospital 3\n 2: 6 Port Hospital 4\n 3: 7 Port Hospital 8\n 4: 8 St. Mark's Maternity Hospital (SMMH) 5\n 5: 8 Military Hospital 9\n 6: 8 Other 10\n 7: 8 Port Hospital 10\n 8: 9 Port Hospital 28\n 9: 9 Missing 27\n10: 9 Central Hospital 10\n11: 9 St. Mark's Maternity Hospital (SMMH) 6\n12: 10 Missing 2\n13: 10 Military Hospital 3\n14: 3 Port Hospital 1\n15: 4 Military Hospital 1\n16: 5 Other 2\n17: 5 Central Hospital 1\n18: 5 Missing 1\n19: 6 Missing 7\n20: 6 St. Mark's Maternity Hospital (SMMH) 2\n21: 6 Military Hospital 1\n22: 7 Military Hospital 3\n23: 7 Other 1\n24: 7 Missing 2\n25: 7 St. Mark's Maternity Hospital (SMMH) 1\n26: 8 Central Hospital 2\n27: 8 Missing 6\n28: 9 Other 9\n29: 9 Military Hospital 11\n30: 10 Port Hospital 3\n31: 10 Other 4\n32: 10 St. Mark's Maternity Hospital (SMMH) 1\n33: 10 Central Hospital 1\n34: 11 Missing 2\n35: 11 Port Hospital 1\n36: 12 Port Hospital 1\n month hospital N\n\n\nData.table also allows the chaining expressions as follows:\n\nlinelist[, .N, .(hospital)][order(-N)][1:3] #1st selects all cases by hospital, 2nd orders the cases in descending order, 3rd subsets the 3 hospitals with the largest caseload\n\n hospital N\n <char> <int>\n1: Port Hospital 1762\n2: Missing 1469\n3: Military Hospital 896\n\n\nIn these examples we are following the assumption that a row in the data table is equal to a new case, and so we can use the .N to represent the number of rows in the data table. Another useful function to represent the number of unique cases is uniqueN(), which returns the number of unique values in a given input. This is illustrated here:\n\nlinelist[, .(uniqueN(gender))] #remember .() in the j argument returns a data table\n\n V1\n <int>\n1: 3\n\n\nThe answer is 3, as the unique values in the gender column are m, f and N/A. Compare with the base R function unique(), which returns all the unique values in a given input:\n\nlinelist[, .(unique(gender))]\n\n V1\n <char>\n1: m\n2: f\n3: <NA>\n\n\nTo find the number of unique cases in a given month we would write the following:\n\nlinelist[, .(uniqueN(case_id)), .(month = month(date_hospitalisation))]\n\n month V1\n <num> <int>\n 1: 5 62\n 2: 6 100\n 3: 7 198\n 4: 8 509\n 5: 9 1170\n 6: 10 1228\n 7: 11 813\n 8: 12 576\n 9: 1 434\n10: 2 310\n11: 3 290\n12: 4 198", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#adding-and-updating-to-data-tables", "href": "new_pages/data_table.html#adding-and-updating-to-data-tables", - "title": "50  Data Table", - "section": "50.6 Adding and updating to data tables", - "text": "50.6 Adding and updating to data tables\nThe := operator is used to add or update data in a data table. Adding columns to your data table can be done in the following ways:\n\nlinelist[, adult := age >= 18] #adds one column\nlinelist[, c(\"child\", \"wt_lbs\") := .(age < 18, wt_kg*2.204)] #to add multiple columns requires c(\"\") and list() or .() syntax\nlinelist[, `:=` (bmi_in_range = (bmi > 16 & bmi < 40),\n no_infector_source_data = is.na(infector) | is.na(source))] #this method uses := as a functional operator `:=`\nlinelist[, adult := NULL] #deletes the column\n\nFurther complex aggregations are beyond the scope of this introductory chapter, but the idea is to provide a popular and viable alternative to dplyr for grouping and cleaning data. The data.table package is a great package that allows for neat and readable code.", + "title": "51  Data Table", + "section": "51.6 Adding and updating to data tables", + "text": "51.6 Adding and updating to data tables\nThe := operator is used to add or update data in a data table. Adding columns to your data table can be done in the following ways:\n\nlinelist[, adult := age >= 18] #adds one column\nlinelist[, c(\"child\", \"wt_lbs\") := .(age < 18, wt_kg*2.204)] #to add multiple columns requires c(\"\") and list() or .() syntax\nlinelist[, `:=` (bmi_in_range = (bmi > 16 & bmi < 40),\n no_infector_source_data = is.na(infector) | is.na(source))] #this method uses := as a functional operator `:=`\nlinelist[, adult := NULL] #deletes the column\n\nFurther complex aggregations are beyond the scope of this introductory chapter, but the idea is to provide a popular and viable alternative to dplyr for grouping and cleaning data. The data.table package is a great package that allows for neat and readable code.", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" ] }, { "objectID": "new_pages/data_table.html#resources", "href": "new_pages/data_table.html#resources", - "title": "50  Data Table", - "section": "50.7 Resources", - "text": "50.7 Resources\nHere are some useful resources for more information: * https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html * https://github.com/Rdatatable/data.table * https://s3.amazonaws.com/assets.datacamp.com/img/blog/data+table+cheat+sheet.pdf * https://www.machinelearningplus.com/data-manipulation/datatable-in-r-complete-guide/ * https://www.datacamp.com/community/tutorials/data-table-r-tutorial\nYou can perform any summary function on grouped data; see the Cheat Sheet here for more info: https://s3.amazonaws.com/assets.datacamp.com/blog_assets/datatable_Cheat_Sheet_R.pdf", + "title": "51  Data Table", + "section": "51.7 Resources", + "text": "51.7 Resources\nHere are some useful resources for more information:\n\ndata.table vignette\ndata.table github\ndata.table cheatsheet\nGuide to data.table\ndata.table tutorial", "crumbs": [ "Miscellaneous", - "50  Data Table" + "51  Data Table" + ] + }, + { + "objectID": "new_pages/epicurves.html#incidence2", + "href": "new_pages/epicurves.html#incidence2", + "title": "32  Epidemic curves", + "section": "32.7 incidence2", + "text": "32.7 incidence2\nBelow we demonstrate how to make epicurves using the incidence2 package. The authors of this package have tried to allow the user to create and modify epicurves without needing to know ggplot2 syntax. Much of this page is adapted from the package vignettes, which can be found at the incidence2 github page.\nTo create an epicurve with incidence2 you need to have a column with a date value (it does not need to be of the class Date, but it should have a numeric or logical order to it (i.e. “Week1”, “Week2”, etc)) and a column with a count variable (what is being counted). It should also not have any duplicated rows.\nTo create this, we can use the function incidence() which will summarise our data in a format that can be used to create epicurves. There are a number of different arguments to incidence(), type ?incidence in your R console to learn more.\n\n#Load package\npacman::p_load(\n incidence2\n)\n\n#Create the incidence object\nepi_day <- linelist %>%\n incidence(date_index = \"date_onset\",\n interval = \"day\")\n\nepi_day\n\n# incidence: 367 x 3\n# count vars: date_onset\n date_index count_variable count\n <date> <chr> <int>\n 1 2014-04-07 date_onset 1\n 2 2014-04-15 date_onset 1\n 3 2014-04-21 date_onset 2\n 4 2014-04-25 date_onset 1\n 5 2014-04-26 date_onset 1\n 6 2014-04-27 date_onset 1\n 7 2014-05-01 date_onset 2\n 8 2014-05-03 date_onset 1\n 9 2014-05-04 date_onset 1\n10 2014-05-05 date_onset 1\n# ℹ 357 more rows\n\n\nThe object created by the function incidence() looks like a data frame, but has its own class of incidence2.\n\nclass(epi_day)\n\n[1] \"incidence2\" \"tbl_df\" \"tbl\" \"data.frame\"\n\n\nTo plot the epicurve, you use the function plot(). To read the incidence2 specific documentation on plotting, type ?plot.incidence2.\n\nplot(epi_day)\n\n\n\n\n\n\n\n\nIf you notice lots of tiny white vertical lines, try to adjust the size of your image. For example, if you export your plot with ggsave(), you can provide numbers to width = and height =. If you widen the plot those lines may disappear.\n\nChange time interval of case aggregation\nThe interval = argument of incidence() defines how the observations are grouped into vertical bars.\nSpecify interval\nincidence2 provides flexibility and understandable syntax for specifying how you want to aggregate your cases into epicurve bars. Provide a value like the ones below to the interval = argument. Below are examples of how different intervals look when applied to the linelist. Note how the default format and frequency of the date labels on the x-axis change as the date interval changes.\n\n# Create the incidence objects (with different intervals)\nepi_epiweek <- incidence(linelist, \"date_onset\", interval = \"epiweek\") # epiweek \nepi_month <- incidence(linelist, \"date_onset\", interval = \"month\") # Monthly\nepi_quarter <- incidence(linelist, \"date_onset\", interval = \"quarter\") # Quarterly\nepi_year <- incidence(linelist, \"date_onset\", interval = \"year\") # Years\n\n# Plot the incidence objects (+ titles for clarity)\n############################\nplot(epi_epiweek) + labs(title = \"2 (Monday) weeks\")\nplot(epi_month) + labs(title = \"Months\")\nplot(epi_quarter) + labs(title = \"Quarters\")\nplot(epi_year) + labs(title = \"Years\")\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFirst date\nYou can optionally specify a value of class Date (e.g. as.Date(\"2016-05-01\")) to firstdate = in the incidence() command. If given, the data will be trimmed to this range and the intervals will begin on this date.\n\n\nGroups\nGroups are specified in the incidence() command, and can be used to color the bars or to facet the data. To specify groups in your data provide the column name(s) to the groups = argument in the incidence() command (no quotes around the column name). If specifying multiple columns, put their names within c().\nYou can specify that cases with missing values in the grouping columns be listed as a distinct NA group by setting na_as_group = TRUE. Otherwise, they will be excluded from the plot.\n\nTo color the bars by a grouping column, you must again provide the column name to fill = in the plot() command\n\nTo facet based on a grouping column, see the section below on facets with incidence2\n\nIn the example below, the cases in the whole outbreak are grouped by their age category. Missing values are included as a group. The epicurve interval is weeks.\n\n# Create incidence object, with data grouped by age category\nage_outbreak <- incidence(\n linelist, # dataset\n date_index = \"date_onset\", # date column\n interval = \"week\", # Monday weekly aggregation of cases\n groups = \"age_cat\" # age_cat is set as a group\n)\n\n# plot the grouped incidence object\nplot(\n age_outbreak, # incidence object with age_cat as group\n fill = \"age_cat\") + # age_cat is used for bar fill color (must have been set as a groups column above)\nlabs(fill = \"Age Category\") # change legend title from default \"age_cat\" (this is a ggplot2 modification)\n\n\n\n\n\n\n\n\nTIP: Change the title of the legend by adding + the ggplot2 command labs(fill = \"your title\") to your incidence2 plot.\nYou can also have the grouped bars display side-by-side by setting stack = FALSE in plot(), as shown below:\n\n# Make incidence object of monthly counts. \nmonthly_gender <- incidence(\n linelist,\n date_index = \"date_onset\",\n interval = \"month\",\n groups = \"gender\" # set gender as grouping column\n)\n\nplot(\n monthly_gender, # incidence object\n fill = \"gender\", # display bars colored by gender\n stack = FALSE) # side-by-side (not stacked)\n\n\n\n\n\n\n\n\nYou can set the na_as_group = argument to FALSE in the incidence() command to remove rows with missing values from the plot.\n\n\nFiltered data\nTo plot the epicurve of a subset of data:\n\nFilter the linelist data\nProvide the filtered data to the incidence() command\nPlot the incidence object\n\nThe example below uses data filtered to show only cases at Central Hospital.\n\n# filter the linelist\ncentral_data <- linelist %>% \n filter(hospital == \"Central Hospital\")\n\n# create incidence object using filtered data\ncentral_outbreak <- incidence(central_data, date_index = \"date_onset\", interval = \"week\")\n\n# plot the incidence object\nplot(central_outbreak, title = \"Weekly case incidence at Central Hospital\")\n\n\n\n\n\n\n\n\n\n\nAggregated counts\nIf your original data are aggregated (counts), provide the name of the column that contains the case counts to the count = argument when creating the incidence object with incidence().\nFor example, this data frame count_data is the linelist aggregated into daily counts by hospital. The first 50 rows look like this:\n\n\n\n\n\n\nIf you are beginning your analysis with daily count data like the dataset above, your incidence() command to convert this to a weekly epicurve by hospital would look like this:\n\nepi_counts <- incidence( # create weekly incidence object\n count_data, # dataset with counts aggregated by day\n date_index = \"date_hospitalisation\", # column with dates\n counts = \"n_cases\", # column with counts\n interval = \"week\", # aggregate daily counts up to weeks\n groups = \"hospital\" # group by hospital\n )\n\n# plot the weekly incidence epi curve, with stacked bars by hospital\nplot(epi_counts, # incidence object\n fill = \"hospital\") # color the bars by hospital\n\n\n\n\n\n\n\n\n\n\nFacets/small multiples\nBelow, we set both columns hospital and outcome as grouping columns in the incidence() command. For grouped data, the plot method will create a faceted plot across groups unless a fill variable is specified. Note we are dropping NA values only for visualisation purposes.\n\nepi_wks_hosp_out <- incidence(\n linelist %>%\n select(date_onset,\n outcome,\n gender) %>%\n drop_na(), # dataset\n date_index = \"date_onset\", # date column\n interval = \"quarter\", # monthly bars \n groups = c(\"outcome\", \"gender\") # both outcome and hospital are given as grouping columns\n )\n\n# plot\nplot(epi_wks_hosp_out)\n\n\n\n\n\n\n\n\nNote that the package ggtree (used for displaying phylogenetic trees) also has a function facet_plot().\n\n\nModifications with plot() and using ggplot2\nTo see further examples of plot modification, please see the incidence2 vignette on customizing incidence plots.\nAdditionally, you can use ggplot2() commands after calling plot(incidence) to fully customise your plots.\nFor example here we are going to change the theme, axis labels, and add a trend line (as specified in the chapter on Moving averages) of the first plot we created with the object epi_day.\n\nplot(epi_day) +\n # plot moving average\n tidyquant::geom_ma( \n mapping = aes(x = date_index,\n y = count), \n n = 7, \n size = 1,\n color = \"red\",\n linetype = \"solid\") +\n labs(x = \"Onset date\",\n y = \"Daily cases\",\n title = \"Epicurve of Ebola cases\",\n fill = \"\") +\n theme_dark() +\n theme(legend.position = \"none\")", + "crumbs": [ + "Data Visualization", + "32  Epidemic curves" + ] + }, + { + "objectID": "new_pages/regression.html#preparation-preparation", + "href": "new_pages/regression.html#preparation-preparation", + "title": "19  Univariate and multivariable regression", + "section": "", + "text": "Load packages\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.\n\npacman::p_load(\n rio, # File import\n here, # File locator\n tidyverse, # data management + ggplot2 graphics, \n stringr, # manipulate text strings \n purrr, # loop over objects in a tidy way\n gtsummary, # summary statistics and tests \n broom, # tidy up results from regressions\n lmtest, # likelihood-ratio tests\n parameters, # alternative to tidy up results from regressions\n see # alternative to visualise forest plots\n )\n\n\n\nImport data\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).\n\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n\nThe first 50 rows of the linelist are displayed below.\n\n\n\n\n\n\n\n\nClean data\n\nStore explanatory variables\nWe store the names of the explanatory columns as a character vector. This will be referenced later.\n\n## define variables of interest \nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n\n\nConvert to 1’s and 0’s\nBelow we convert the explanatory columns from “yes”/“no”, “m”/“f”, and “dead”/“alive” to 1 / 0, to cooperate with the expectations of logistic regression models. To do this efficiently, used across() from dplyr to transform multiple columns at one time. The function we apply to each column is case_when() (also dplyr) which applies logic to convert specified values to 1’s and 0’s. See sections on across() and case_when() in the Cleaning data and core functions page.\nNote: the “.” below represents the column that is being processed by across() at that moment.\n\n## convert dichotomous variables to 0/1 \nlinelist <- linelist %>% \n mutate(across( \n .cols = all_of(c(explanatory_vars, \"outcome\")), ## for each column listed and \"outcome\"\n .fns = ~case_when( \n . %in% c(\"m\", \"yes\", \"Death\") ~ 1, ## recode male, yes and death to 1\n . %in% c(\"f\", \"no\", \"Recover\") ~ 0, ## female, no and recover to 0\n TRUE ~ NA_real_) ## otherwise set to missing\n )\n )\n\n\n\nDrop rows with missing values\nTo drop rows with missing values, can use the tidyr function drop_na(). However, we only want to do this for rows that are missing values in the columns of interest.\nThe first thing we must to is make sure our explanatory_vars vector includes the column age (age would have produced an error in the previous case_when() operation, which was only for dichotomous variables). Then we pipe the linelist to drop_na() to remove any rows with missing values in the outcome column or any of the explanatory_vars columns.\nBefore running the code, the number of rows in the linelist is nrow(linelist).\n\n## add in age_category to the explanatory vars \nexplanatory_vars <- c(explanatory_vars, \"age_cat\")\n\n## drop rows with missing information for variables of interest \nlinelist <- linelist %>% \n drop_na(any_of(c(\"outcome\", explanatory_vars)))\n\nThe number of rows remaining in linelist is nrow(linelist).", + "crumbs": [ + "Analysis", + "19  Univariate and multivariable regression" + ] + }, + { + "objectID": "new_pages/regression.html#model-performance", + "href": "new_pages/regression.html#model-performance", + "title": "19  Univariate and multivariable regression", + "section": "19.5 Model performance", + "text": "19.5 Model performance\nOnce you have built your regression models, you may want to assess how well the model has fit the data. There are many different approaches to do this, and many different metrics with which to assess your model fit, and how it compares with other model formulations. How you assess your model fit will depend on your model, the data, and the context in which you are conducting your work.\nWhile there are many different functions, and many different packages, to assess model fit, one package that nicely combines several different metrics and approaches into a single source is the performance package. This package allows you to assess model assumptions (such as linearity, homogeneity, highlight outliers, etc.) and check how well the model performs (Akaike Information Criterion values, R2, RMSE, etc) with a few simple functions.\nUnfortunately, we are unable to use this package with gtsummary, but it readily accepts objects generated by other packages such as stats, lmerMod and tidymodels. Here we will demonstrate its application using the function glm() for a multivariable regression. To do this we can use the function performance() to assess model fit, and compare_perfomrance() to compare the two models.\n\n#Load in packages\npacman::p_load(performance)\n\n#Set up regression models\nregression_one <- linelist %>%\n select(outcome, gender, fever, chills, cough) %>%\n glm(formula = outcome ~ .,\n family = binomial)\n\nregression_two <- linelist %>%\n select(outcome, days_onset_hosp, aches, vomit, age_years) %>%\n glm(formula = outcome ~ .,\n family = binomial)\n\n#Assess model fit\nperformance(regression_one)\n\n# Indices of model performance\n\nAIC | AICc | BIC | Tjur's R2 | RMSE | Sigma | Log_loss | Score_log | PCP\n-----------------------------------------------------------------------------------------\n5719.746 | 5719.760 | 5751.421 | 6.342e-04 | 0.496 | 1.000 | 0.685 | -Inf | 0.508\n\nperformance(regression_two)\n\n# Indices of model performance\n\nAIC | AICc | BIC | Tjur's R2 | RMSE | Sigma | Log_loss | Score_log | PCP\n-----------------------------------------------------------------------------------------\n5411.752 | 5411.767 | 5443.187 | 0.012 | 0.493 | 1.000 | 0.680 | -Inf | 0.513\n\n#Compare model fit\ncompare_performance(regression_one,\n regression_two)\n\nWhen comparing models, please note that probably not all models were fit\n from same data.\n\n\n# Comparison of Model Performance Indices\n\nName | Model | AIC (weights) | AICc (weights) | BIC (weights) | Tjur's R2 | RMSE | Sigma | Log_loss | Score_log | PCP\n------------------------------------------------------------------------------------------------------------------------------------\nregression_one | glm | 5719.7 (<.001) | 5719.8 (<.001) | 5751.4 (<.001) | 6.342e-04 | 0.496 | 1.000 | 0.685 | -Inf | 0.508\nregression_two | glm | 5411.8 (>.999) | 5411.8 (>.999) | 5443.2 (>.999) | 0.012 | 0.493 | 1.000 | 0.680 | -Inf | 0.513\n\n\nFor further reading on the performance package, and the model tests you can carry out, see their github.", + "crumbs": [ + "Analysis", + "19  Univariate and multivariable regression" + ] + }, + { + "objectID": "new_pages/survey_analysis.html#weighted-regression", + "href": "new_pages/survey_analysis.html#weighted-regression", + "title": "26  Survey analysis", + "section": "26.10 Weighted regression", + "text": "26.10 Weighted regression\nAnother tool we can use to analyse our survey data is to use weighted regression. This allows us to carry out to account for the survey design in our regression in order to avoid biases that may be introduced from the survey process.\nTo carry out a univariate regression, we can use the packages survey for the function svyglm() and the package gtsummary which allows us to call svyglm() inside the function tbl_uvregression. To do this we first use the survey_design object created above. This is then provided to the function tbl_uvregression() as in the Univariate and multivariable regression chapter. We then make one key change, we change method = glm to method = survey::svyglm in order to carry out our survey weighted regression.\nHere we will be using the previously created object survey_design to predict whether the value in the column died is TRUE, using the columns malaria_treatment, bednet, and age_years.\n\nsurvey_design %>%\n tbl_uvregression( #Carry out a univariate regression, if we wanted a multivariable regression we would use tbl_\n method = survey::svyglm, #Set this to survey::svyglm to carry out our weighted regression on the survey data\n y = died, #The column we are trying to predict\n method.args = list(family = binomial), #The family, we are carrying out a logistic regression so we want the family as binomial\n include = c(malaria_treatment, #These are the columns we want to evaluate\n bednet,\n age_years),\n exponentiate = T #To transform the log odds to odds ratio for easier interpretation\n )\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nN\nOR\n1\n95% CI\n1\np-value\n\n\n\n\nmalaria_treatment\n1,357,145\n\n\n\n\n\n\n\n\n    FALSE\n\n\n—\n—\n\n\n\n\n    TRUE\n\n\n1.12\n0.33, 3.82\n0.8\n\n\nbednet\n1,482,457\n\n\n\n\n\n\n\n\n    FALSE\n\n\n—\n—\n\n\n\n\n    TRUE\n\n\n0.50\n0.13, 1.88\n0.3\n\n\nage_years\n1,482,457\n1.01\n1.00, 1.02\n0.013\n\n\n\n1\nOR = Odds Ratio, CI = Confidence Interval\n\n\n\n\n\n\n\n\nIf we wanted to carry out a multivariable regression, we would have to first use the function svyglm() and pipe (%>%) the results into the function tbl_regression. Note that we need to specify the formula.\n\nsurvey_design %>%\n svyglm(formula = died ~ malaria_treatment + \n bednet + \n age_years,\n family = binomial) %>% #The family, we are carrying out a logistic regression so we want the family as binomial\n tbl_regression( \n exponentiate = T #To transform the log odds to odds ratio for easier interpretation \n )\n\n\n\n\n\n\n\n\n\n\n\n\n\nCharacteristic\nOR\n1\n95% CI\n1\np-value\n\n\n\n\nmalaria_treatment\n\n\n\n\n\n\n\n\n    FALSE\n—\n—\n\n\n\n\n    TRUE\n1.11\n0.28, 4.40\n0.9\n\n\nbednet\n\n\n\n\n\n\n\n\n    FALSE\n—\n—\n\n\n\n\n    TRUE\n0.62\n0.12, 3.21\n0.5\n\n\nage_years\n1.01\n1.00, 1.02\n0.018\n\n\n\n1\nOR = Odds Ratio, CI = Confidence Interval", + "crumbs": [ + "Analysis", + "26  Survey analysis" + ] + }, + { + "objectID": "new_pages/quarto.html", + "href": "new_pages/quarto.html", + "title": "44  Quarto", + "section": "", + "text": "44.1 Why Quarto over R Markdown?\nQuarto provides the flexibility and automation that R Markdown pioneered, with a more powerful underlying flexibility that streamlines a lot of the sharing and publishing processes of your work! While R Markdown is tied to R, Quarto documents allow you to use a number of different programming languages such as R, Python, Javascript and Julia. While R Markdown can do this, the approach is less straightforward. By streamlining the inclusion of multiple programming languages, it makes collaborating across different people and groups, easier if multiple approaches are used.\nEven if you are only working in R, Quarto has several advantages. Rather than relying on individual packages to create different outputs, Quarto handles this all internally. To produce a website, PowerPoint slides, or a html report, rather than using external packages, Quarto has this functionality inbuilt. This means you have fewer packages to update, and if one of the packages is no longer maintained, you don’t need to change your approach to maintain the same output.\nQuarto also has the advantage of providing a way to render multiple files at the same time, and combine them when wanted. The Epi R Handbook is written as several individual Quarto files, and then combined to make this website!\nIf you are thinking of moving over from R Markdown to Quarto, don’t worry, you won’t have to re-write every R Markdown document to move over to Quarto! Quarto documents follow the same syntax and commands as Rmarkdown. Previous scripts in R Markdown can generally be moved over to Quarto without changing any of your code!\nBelow is the pipeline through which Quarto documents are created, as you can see it is very similar to the R Markdown pipeline, other than the initial file type as changed.\nSource", + "crumbs": [ + "Reports and dashboards", + "44  Quarto" + ] + }, + { + "objectID": "new_pages/quarto.html#getting-started", + "href": "new_pages/quarto.html#getting-started", + "title": "44  Quarto", + "section": "44.2 Getting started", + "text": "44.2 Getting started\nCreating a Quarto document does not require us to download an additional package, unlike R Markdown which requires the rmarkdown package. Additionally, you do not need to install LaTex, as you do with R Markdown, as Quarto contains built in functionality.\nFunctionally, Quarto works the same way as R Markdown. You create your Quarto script (instead of your R Markdown file), write your code, and knit the document.\nFirst, just like when you create an R Markdown document in Rstudio you start with File, then New file, then R Markdown.\n\n\n\n\n\n\n\n\n\nYou will then get a number of different options to choose. Here we will select “HTML” to create an html document. All these details can be changed later in the document, so do not worry if you change your mind later.\n\n\n\n\n\n\n\n\n\nThis will create your new Quarto script. Note: While the R Markdown scripts ended with .Rmd, Quarto scripts end with .qmd\nWhile R Markdown scripts set the working directory to wherever the file is located, Quarto documents retain the original working directory. This is especially useful when you are working with an R Project.\nLike for R Markdown, Quarto used in RStudio allows you to see what the rendered document will look like after it has been knit. To switch between the “Visual” and “Source” mode, click the “Visual” button in the top left side of the script.\n\n\n\n\n\n\n\n\n\nYou are now ready to code your Quarto script! The syntax and approach is the same as creating an R Markdown document, so see the chapter on Reports with R Markdown for guidance and inspiration.\nHere is an example of what a Quarto script for analysing our linelist data set might look like.\n\n\n\n\n\n\n\n\n\nAnd here is what the output looks like.", + "crumbs": [ + "Reports and dashboards", + "44  Quarto" + ] + }, + { + "objectID": "new_pages/quarto.html#moving-beyond-simple-reports", + "href": "new_pages/quarto.html#moving-beyond-simple-reports", + "title": "44  Quarto", + "section": "44.3 Moving beyond simple reports", + "text": "44.3 Moving beyond simple reports\nYou may want to move beyond creating simple, static, reports to interactive dashboards and outputs, like you can in R Markdown. Luckily you can do all of this in Quarto, using inbuilt functionality, and other packages like Shiny! For an example of how far you can take your Quarto scripts, see this Quarto Gallery.", + "crumbs": [ + "Reports and dashboards", + "44  Quarto" + ] + }, + { + "objectID": "new_pages/quarto.html#resources", + "href": "new_pages/quarto.html#resources", + "title": "44  Quarto", + "section": "44.4 Resources", + "text": "44.4 Resources\nI’m an R user: Quarto or R Markdown?\nFAQ for R Markdown Users\nQuarto tutorial\nQuarto Gallery\nCreate & Publish a Quarto Blog on QUarto Pub in 100 seconds", + "crumbs": [ + "Reports and dashboards", + "44  Quarto" + ] + }, + { + "objectID": "new_pages/characters_strings.html#regular-expressions-regex-and-special-characters", + "href": "new_pages/characters_strings.html#regular-expressions-regex-and-special-characters", + "title": "10  Characters and strings", + "section": "10.7 Regular expressions (regex) and special characters", + "text": "10.7 Regular expressions (regex) and special characters\nRegular expressions, or “regex”, is a concise language for describing patterns in strings. If you are not familiar with it, a regular expression can look like an alien language. Here we try to de-mystify this language a little bit.\nMuch of this section is adapted from this tutorial and this cheatsheet. We selectively adapt here knowing that this handbook might be viewed by people without internet access to view the other tutorials.\nA regular expression is often applied to extract specific patterns from “unstructured” text - for example medical notes, chief complaints, patient history, or other free text columns in a data frame\nThere are four basic tools one can use to create a basic regular expression:\n\nCharacter sets.\n\nMeta characters.\n\nQuantifiers.\n\nGroups.\n\nCharacter sets\nCharacter sets, are a way of expressing listing options for a character match, within brackets. So any a match will be triggered if any of the characters within the brackets are found in the string. For example, to look for vowels one could use this character set: “[aeiou]”. Some other common character sets are:\n\n\n\n\n\n\n\nCharacter set\nMatches for\n\n\n\n\n\"[A-Z]\"\nany single capital letter\n\n\n\"[a-z]\"\nany single lowercase letter\n\n\n\"[0-9]\"\nany digit\n\n\n[:alnum:]\nany alphanumeric character\n\n\n[:digit:]\nany numeric digit\n\n\n[:alpha:]\nany letter (upper or lowercase)\n\n\n[:upper:]\nany uppercase letter\n\n\n[:lower:]\nany lowercase letter\n\n\n\nCharacter sets can be combined within one bracket (no spaces!), such as \"[A-Za-z]\" (any upper or lowercase letter), or another example \"[t-z0-5]\" (lowercase t through z OR number 0 through 5).\nMeta characters\nMeta characters are shorthand for character sets. Some of the important ones are listed below:\n\n\n\n\n\n\n\nMeta character\nRepresents\n\n\n\n\n\"\\\\s\"\na single space\n\n\n\"\\\\w\"\nany single alphanumeric character (A-Z, a-z, or 0-9)\n\n\n\"\\\\d\"\nany single numeric digit (0-9)\n\n\n\nQuantifiers\nTypically you do not want to search for a match on only one character. Quantifiers allow you to designate the length of letters/numbers to allow for the match.\nQuantifiers are numbers written within curly brackets { } after the character they are quantifying, for example:\n\n\"A{2}\" will return instances of two capital A letters.\n\n\"A{2,4}\" will return instances of between two and four capital A letters (do not put spaces!).\n\n\"A{2,}\" will return instances of two or more capital A letters.\n\n\"A+\" will return instances of one or more capital A letters (group extended until a different character is encountered).\n\nPrecede with an * asterisk to return zero or more matches (useful if you are not sure the pattern is present).\n\nUsing the + plus symbol as a quantifier, the match will occur until a different character is encountered. For example, this expression will return all words (alpha characters: \"[A-Za-z]+\"\n\n# test string for quantifiers\ntest <- \"A-AA-AAA-AAAA\"\n\nWhen a quantifier of {2} is used, only pairs of consecutive A’s are returned. Two pairs are identified within AAAA.\n\nstr_extract_all(test, \"A{2}\")\n\n[[1]]\n[1] \"AA\" \"AA\" \"AA\" \"AA\"\n\n\nWhen a quantifier of {2,4} is used, groups of consecutive A’s that are two to four in length are returned.\n\nstr_extract_all(test, \"A{2,4}\")\n\n[[1]]\n[1] \"AA\" \"AAA\" \"AAAA\"\n\n\nWith the quantifier +, groups of one or more are returned:\n\nstr_extract_all(test, \"A+\")\n\n[[1]]\n[1] \"A\" \"AA\" \"AAA\" \"AAAA\"\n\n\nRelative position\nThese express requirements for what precedes or follows a pattern. For example, to extract sentences, “two numbers that are followed by a period” (\"\"). (?<=\\.)\\s(?=[A-Z])\n\nstr_extract_all(test, \"\")\n\n[[1]]\n [1] \"A\" \"-\" \"A\" \"A\" \"-\" \"A\" \"A\" \"A\" \"-\" \"A\" \"A\" \"A\" \"A\"\n\n\n\n\n\n\n\n\n\nPosition statement\nMatches to\n\n\n\n\n\"(?<=b)a\"\n“a” that is preceded by a “b”\n\n\n\"(?<!b)a\"\n“a” that is NOT preceded by a “b”\n\n\n\"a(?=b)\"\n“a” that is followed by a “b”\n\n\n\"a(?!b)\"\n“a” that is NOT followed by a “b”\n\n\n\nGroups\nCapturing groups in your regular expression is a way to have a more organized output upon extraction.\nRegex examples\nBelow is a free text for the examples. We will try to extract useful information from it using a regular expression search term.\n\npt_note <- \"Patient arrived at Broward Hospital emergency ward at 18:00 on 6/12/2005. Patient presented with radiating abdominal pain from LR quadrant. Patient skin was pale, cool, and clammy. Patient temperature was 99.8 degrees farinheit. Patient pulse rate was 100 bpm and thready. Respiratory rate was 29 per minute.\"\n\nThis expression matches to all words (any character until hitting non-character such as a space):\n\nstr_extract_all(pt_note, \"[A-Za-z]+\")\n\n[[1]]\n [1] \"Patient\" \"arrived\" \"at\" \"Broward\" \"Hospital\" \n [6] \"emergency\" \"ward\" \"at\" \"on\" \"Patient\" \n[11] \"presented\" \"with\" \"radiating\" \"abdominal\" \"pain\" \n[16] \"from\" \"LR\" \"quadrant\" \"Patient\" \"skin\" \n[21] \"was\" \"pale\" \"cool\" \"and\" \"clammy\" \n[26] \"Patient\" \"temperature\" \"was\" \"degrees\" \"farinheit\" \n[31] \"Patient\" \"pulse\" \"rate\" \"was\" \"bpm\" \n[36] \"and\" \"thready\" \"Respiratory\" \"rate\" \"was\" \n[41] \"per\" \"minute\" \n\n\nThe expression \"[0-9]{1,2}\" matches to consecutive numbers that are 1 or 2 digits in length. It could also be written \"\\\\d{1,2}\", or \"[:digit:]{1,2}\".\n\nstr_extract_all(pt_note, \"[0-9]{1,2}\")\n\n[[1]]\n [1] \"18\" \"00\" \"6\" \"12\" \"20\" \"05\" \"99\" \"8\" \"10\" \"0\" \"29\"\n\n\n\n\n\n\nYou can view a useful list of regex expressions and tips on page 2 of this cheatsheet\nAlso see this tutorial.", + "crumbs": [ + "Data Management", + "10  Characters and strings" + ] + }, + { + "objectID": "new_pages/quarto.html#why-quarto-over-r-markdown", + "href": "new_pages/quarto.html#why-quarto-over-r-markdown", + "title": "44  Quarto", + "section": "", + "text": "Limitations of Quarto\nThere is one substantial limitation of Quarto, compared to R Markdown, when it is used to generate multiple different reports using the same dataset.\nIn R Markdown, you can load in a dataset, run your analyses, and then generate multiple different reports from this dataset in your R environment. This means that you do not need to individually import the dataset for each report.\nHowever, because of the way that Quarto works, every single report needs to be self contained. That is to say that you would need to import the dataset every time you want a new report.\nThis is not a problem if you are only running a single report, or a handful. But if you have a large dataset, and need to make a large number of reports, this can quickly become a real problem.\nImagine a scenario where you are conducting a national outbreak response. You have a dataset which takes 5 minutes to import, and then you need to generate a report for each of the 50 provinces in the country.\n\nFor R Markdown, you would only need to read in the data once, and then run your reports. This would take a minimum of 5 minutes (5 minutes to read in the data + time to run the script for the reports).\nFor Quarto, you would need to read in the dataset each time. This would mean you would need at least 250 minutes (5 minutes for each of the 50 provinces).\n\nThis quickly increasing time burden means that if you are generating multiple reports based on a large dataset, you may want to use R Markdown over Quarto!\nFor further explanation, please see these forum posts:\nQuarto can’t find R-objects\nHow to quarto_render into console environment", + "crumbs": [ + "Reports and dashboards", + "44  Quarto" ] } ] \ No newline at end of file diff --git a/html_outputs/site_libs/bootstrap/bootstrap-dark.min.css b/html_outputs/site_libs/bootstrap/bootstrap-dark.min.css index 3afbe952..94743bac 100644 --- a/html_outputs/site_libs/bootstrap/bootstrap-dark.min.css +++ b/html_outputs/site_libs/bootstrap/bootstrap-dark.min.css @@ -2,11 +2,11 @@ * Bootstrap v5.3.1 (https://getbootstrap.com/) * Copyright 2011-2023 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */@import"https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400&display=swap";:root,[data-bs-theme=light]{--bs-blue: #375a7f;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #e83e8c;--bs-red: #e74c3c;--bs-orange: #fd7e14;--bs-yellow: #f39c12;--bs-green: #00bc8c;--bs-teal: #20c997;--bs-cyan: #3498db;--bs-black: #000;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #ebebeb;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #444;--bs-gray-800: #343a40;--bs-gray-900: #222;--bs-default: #434343;--bs-primary: #375a7f;--bs-secondary: #434343;--bs-success: #00bc8c;--bs-info: #3498db;--bs-warning: #f39c12;--bs-danger: #e74c3c;--bs-light: #6f6f6f;--bs-dark: #2d2d2d;--bs-default-rgb: 67, 67, 67;--bs-primary-rgb: 55, 90, 127;--bs-secondary-rgb: 67, 67, 67;--bs-success-rgb: 0, 188, 140;--bs-info-rgb: 52, 152, 219;--bs-warning-rgb: 243, 156, 18;--bs-danger-rgb: 231, 76, 60;--bs-light-rgb: 111, 111, 111;--bs-dark-rgb: 45, 45, 45;--bs-primary-text-emphasis: #162433;--bs-secondary-text-emphasis: #1b1b1b;--bs-success-text-emphasis: #004b38;--bs-info-text-emphasis: #153d58;--bs-warning-text-emphasis: #613e07;--bs-danger-text-emphasis: #5c1e18;--bs-light-text-emphasis: #444;--bs-dark-text-emphasis: #444;--bs-primary-bg-subtle: #d7dee5;--bs-secondary-bg-subtle: #d9d9d9;--bs-success-bg-subtle: #ccf2e8;--bs-info-bg-subtle: #d6eaf8;--bs-warning-bg-subtle: #fdebd0;--bs-danger-bg-subtle: #fadbd8;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #afbdcc;--bs-secondary-border-subtle: #b4b4b4;--bs-success-border-subtle: #99e4d1;--bs-info-border-subtle: #aed6f1;--bs-warning-border-subtle: #fad7a0;--bs-danger-border-subtle: #f5b7b1;--bs-light-border-subtle: #ebebeb;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--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: 1rem;--bs-body-font-family: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #fff;--bs-body-color-rgb: 255, 255, 255;--bs-body-bg: #222;--bs-body-bg-rgb: 34, 34, 34;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(255, 255, 255, 0.75);--bs-secondary-color-rgb: 255, 255, 255;--bs-secondary-bg: #ebebeb;--bs-secondary-bg-rgb: 235, 235, 235;--bs-tertiary-color: rgba(255, 255, 255, 0.5);--bs-tertiary-color-rgb: 255, 255, 255;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #00bc8c;--bs-link-color-rgb: 0, 188, 140;--bs-link-decoration: underline;--bs-link-hover-color: #009670;--bs-link-hover-color-rgb: 0, 150, 112;--bs-code-color: #7d12ba;--bs-highlight-bg: #fdebd0;--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(55, 90, 127, 0.25);--bs-form-valid-color: #00bc8c;--bs-form-valid-border-color: #00bc8c;--bs-form-invalid-color: #e74c3c;--bs-form-invalid-border-color: #e74c3c}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #222;--bs-body-bg-rgb: 34, 34, 34;--bs-emphasis-color: #fff;--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: #2b2e31;--bs-tertiary-bg-rgb: 43, 46, 49;--bs-primary-text-emphasis: #879cb2;--bs-secondary-text-emphasis: #8e8e8e;--bs-success-text-emphasis: #66d7ba;--bs-info-text-emphasis: #85c1e9;--bs-warning-text-emphasis: #f8c471;--bs-danger-text-emphasis: #f1948a;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #0b1219;--bs-secondary-bg-subtle: #0d0d0d;--bs-success-bg-subtle: #00261c;--bs-info-bg-subtle: #0a1e2c;--bs-warning-bg-subtle: #311f04;--bs-danger-bg-subtle: #2e0f0c;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #21364c;--bs-secondary-border-subtle: #282828;--bs-success-border-subtle: #007154;--bs-info-border-subtle: #1f5b83;--bs-warning-border-subtle: #925e0b;--bs-danger-border-subtle: #8b2e24;--bs-light-border-subtle: #444;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #879cb2;--bs-link-hover-color: #9fb0c1;--bs-link-color-rgb: 135, 156, 178;--bs-link-hover-color-rgb: 159, 176, 193;--bs-code-color: white;--bs-border-color: #444;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #66d7ba;--bs-form-valid-border-color: #66d7ba;--bs-form-invalid-color: #f1948a;--bs-form-invalid-border-color: #f1948a}*,*::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 #ebebeb}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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:inherit;background-color:#f8f9fa;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:#f8f9fa;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#222;background-color:#fff;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(255,255,255,.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:#222;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(255,255,255,.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: #fff;--bs-table-bg: #222;--bs-table-border-color: #434343;--bs-table-accent-bg: transparent;--bs-table-striped-color: #fff;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #fff;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #fff;--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 #fff}.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: #fff;--bs-table-bg: #375a7f;--bs-table-border-color: #4b6b8c;--bs-table-striped-bg: #416285;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b6b8c;--bs-table-active-color: #fff;--bs-table-hover-bg: #466689;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #fff;--bs-table-bg: #434343;--bs-table-border-color: #565656;--bs-table-striped-bg: #4c4c4c;--bs-table-striped-color: #fff;--bs-table-active-bg: #565656;--bs-table-active-color: #fff;--bs-table-hover-bg: #515151;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #fff;--bs-table-bg: #00bc8c;--bs-table-border-color: #1ac398;--bs-table-striped-bg: #0dbf92;--bs-table-striped-color: #fff;--bs-table-active-bg: #1ac398;--bs-table-active-color: #fff;--bs-table-hover-bg: #13c195;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #fff;--bs-table-bg: #3498db;--bs-table-border-color: #48a2df;--bs-table-striped-bg: #3e9ddd;--bs-table-striped-color: #fff;--bs-table-active-bg: #48a2df;--bs-table-active-color: #fff;--bs-table-hover-bg: #43a0de;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #fff;--bs-table-bg: #f39c12;--bs-table-border-color: #f4a62a;--bs-table-striped-bg: #f4a11e;--bs-table-striped-color: #fff;--bs-table-active-bg: #f4a62a;--bs-table-active-color: #fff;--bs-table-hover-bg: #f4a324;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #fff;--bs-table-bg: #e74c3c;--bs-table-border-color: #e95e50;--bs-table-striped-bg: #e85546;--bs-table-striped-color: #fff;--bs-table-active-bg: #e95e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #e9594b;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #fff;--bs-table-bg: #6f6f6f;--bs-table-border-color: #7d7d7d;--bs-table-striped-bg: #767676;--bs-table-striped-color: #fff;--bs-table-active-bg: #7d7d7d;--bs-table-active-color: #fff;--bs-table-hover-bg: #7a7a7a;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #2d2d2d;--bs-table-border-color: #424242;--bs-table-striped-bg: #383838;--bs-table-striped-color: #fff;--bs-table-active-bg: #424242;--bs-table-active-color: #fff;--bs-table-hover-bg: #3d3d3d;--bs-table-hover-color: #fff;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(255,255,255,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#2d2d2d;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-clip:padding-box;border:1px solid #adb5bd;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:#2d2d2d;background-color:#fff;border-color:#9badbf;outline:0;box-shadow:0 0 0 .25rem rgba(55,90,127,.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:#595959;opacity:1}.form-control:disabled{background-color:#ebebeb;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#6f6f6f;background-color:#434343;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:#363636}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#fff;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:#2d2d2d;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 #adb5bd;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:#9badbf;outline:0;box-shadow:0 0 0 .25rem rgba(55,90,127,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{color:#595959;background-color:#ebebeb}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #2d2d2d}.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: #fff;width:1em;height:1em;margin-top:.25em;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:none;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:#9badbf;outline:0;box-shadow:0 0 0 .25rem rgba(55,90,127,.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:#375a7f;border-color:#375a7f}.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='%23fff' 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='%23fff'/%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:#375a7f;border-color:#375a7f;--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='%23fff' 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='%239badbf'/%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='%23fff'/%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 #222,0 0 0 .25rem rgba(55,90,127,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #222,0 0 0 .25rem rgba(55,90,127,.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:#375a7f;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:#c3ced9}.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:#375a7f;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:#c3ced9}.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(255,255,255,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(255,255,255,.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:#ebebeb}.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:#6f6f6f;text-align:center;white-space:nowrap;background-color:#434343;border:1px solid #adb5bd;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:#00bc8c}.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:#00bc8c;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:#00bc8c;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='%2300bc8c' 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:#00bc8c;box-shadow:0 0 0 .25rem rgba(0,188,140,.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:#00bc8c}.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='%2300bc8c' 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:#00bc8c;box-shadow:0 0 0 .25rem rgba(0,188,140,.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:#00bc8c}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#00bc8c}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(0,188,140,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#00bc8c}.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:#e74c3c}.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:#e74c3c;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:#e74c3c;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='%23e74c3c'%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='%23e74c3c' 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:#e74c3c;box-shadow:0 0 0 .25rem rgba(231,76,60,.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:#e74c3c}.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='%23e74c3c'%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='%23e74c3c' 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:#e74c3c;box-shadow:0 0 0 .25rem rgba(231,76,60,.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:#e74c3c}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#e74c3c}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(231,76,60,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#e74c3c}.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: #fff;--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: #fff;--bs-btn-bg: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #393939;--bs-btn-hover-border-color: #363636;--bs-btn-focus-shadow-rgb: 95, 95, 95;--bs-btn-active-color: #fff;--bs-btn-active-bg: #363636;--bs-btn-active-border-color: #323232;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #434343;--bs-btn-disabled-border-color: #434343}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #375a7f;--bs-btn-border-color: #375a7f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2f4d6c;--bs-btn-hover-border-color: #2c4866;--bs-btn-focus-shadow-rgb: 85, 115, 146;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2c4866;--bs-btn-active-border-color: #29445f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #375a7f;--bs-btn-disabled-border-color: #375a7f}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #393939;--bs-btn-hover-border-color: #363636;--bs-btn-focus-shadow-rgb: 95, 95, 95;--bs-btn-active-color: #fff;--bs-btn-active-bg: #363636;--bs-btn-active-border-color: #323232;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #434343;--bs-btn-disabled-border-color: #434343}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #00bc8c;--bs-btn-border-color: #00bc8c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #00a077;--bs-btn-hover-border-color: #009670;--bs-btn-focus-shadow-rgb: 38, 198, 157;--bs-btn-active-color: #fff;--bs-btn-active-bg: #009670;--bs-btn-active-border-color: #008d69;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #00bc8c;--bs-btn-disabled-border-color: #00bc8c}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #3498db;--bs-btn-border-color: #3498db;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c81ba;--bs-btn-hover-border-color: #2a7aaf;--bs-btn-focus-shadow-rgb: 82, 167, 224;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a7aaf;--bs-btn-active-border-color: #2772a4;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3498db;--bs-btn-disabled-border-color: #3498db}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #f39c12;--bs-btn-border-color: #f39c12;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #cf850f;--bs-btn-hover-border-color: #c27d0e;--bs-btn-focus-shadow-rgb: 245, 171, 54;--bs-btn-active-color: #fff;--bs-btn-active-bg: #c27d0e;--bs-btn-active-border-color: #b6750e;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #f39c12;--bs-btn-disabled-border-color: #f39c12}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #e74c3c;--bs-btn-border-color: #e74c3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #c44133;--bs-btn-hover-border-color: #b93d30;--bs-btn-focus-shadow-rgb: 235, 103, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #b93d30;--bs-btn-active-border-color: #ad392d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #e74c3c;--bs-btn-disabled-border-color: #e74c3c}.btn-light{--bs-btn-color: #fff;--bs-btn-bg: #6f6f6f;--bs-btn-border-color: #6f6f6f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #5e5e5e;--bs-btn-hover-border-color: #595959;--bs-btn-focus-shadow-rgb: 133, 133, 133;--bs-btn-active-color: #fff;--bs-btn-active-bg: #595959;--bs-btn-active-border-color: #535353;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #6f6f6f;--bs-btn-disabled-border-color: #6f6f6f}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #2d2d2d;--bs-btn-border-color: #2d2d2d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #4d4d4d;--bs-btn-hover-border-color: #424242;--bs-btn-focus-shadow-rgb: 77, 77, 77;--bs-btn-active-color: #fff;--bs-btn-active-bg: #575757;--bs-btn-active-border-color: #424242;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2d2d2d;--bs-btn-disabled-border-color: #2d2d2d}.btn-outline-default{--bs-btn-color: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #434343;--bs-btn-hover-border-color: #434343;--bs-btn-focus-shadow-rgb: 67, 67, 67;--bs-btn-active-color: #fff;--bs-btn-active-bg: #434343;--bs-btn-active-border-color: #434343;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #434343;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #434343;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #375a7f;--bs-btn-border-color: #375a7f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #375a7f;--bs-btn-hover-border-color: #375a7f;--bs-btn-focus-shadow-rgb: 55, 90, 127;--bs-btn-active-color: #fff;--bs-btn-active-bg: #375a7f;--bs-btn-active-border-color: #375a7f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #375a7f;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #375a7f;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #434343;--bs-btn-hover-border-color: #434343;--bs-btn-focus-shadow-rgb: 67, 67, 67;--bs-btn-active-color: #fff;--bs-btn-active-bg: #434343;--bs-btn-active-border-color: #434343;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #434343;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #434343;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #00bc8c;--bs-btn-border-color: #00bc8c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #00bc8c;--bs-btn-hover-border-color: #00bc8c;--bs-btn-focus-shadow-rgb: 0, 188, 140;--bs-btn-active-color: #fff;--bs-btn-active-bg: #00bc8c;--bs-btn-active-border-color: #00bc8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #00bc8c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #00bc8c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #3498db;--bs-btn-border-color: #3498db;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3498db;--bs-btn-hover-border-color: #3498db;--bs-btn-focus-shadow-rgb: 52, 152, 219;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3498db;--bs-btn-active-border-color: #3498db;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3498db;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3498db;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #f39c12;--bs-btn-border-color: #f39c12;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #f39c12;--bs-btn-hover-border-color: #f39c12;--bs-btn-focus-shadow-rgb: 243, 156, 18;--bs-btn-active-color: #fff;--bs-btn-active-bg: #f39c12;--bs-btn-active-border-color: #f39c12;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f39c12;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f39c12;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #e74c3c;--bs-btn-border-color: #e74c3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #e74c3c;--bs-btn-hover-border-color: #e74c3c;--bs-btn-focus-shadow-rgb: 231, 76, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #e74c3c;--bs-btn-active-border-color: #e74c3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #e74c3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #e74c3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #6f6f6f;--bs-btn-border-color: #6f6f6f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #6f6f6f;--bs-btn-hover-border-color: #6f6f6f;--bs-btn-focus-shadow-rgb: 111, 111, 111;--bs-btn-active-color: #fff;--bs-btn-active-bg: #6f6f6f;--bs-btn-active-border-color: #6f6f6f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #6f6f6f;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #6f6f6f;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #2d2d2d;--bs-btn-border-color: #2d2d2d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2d2d2d;--bs-btn-hover-border-color: #2d2d2d;--bs-btn-focus-shadow-rgb: 45, 45, 45;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2d2d2d;--bs-btn-active-border-color: #2d2d2d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2d2d2d;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2d2d2d;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #00bc8c;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #009670;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #009670;--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, 198, 157;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: #fff;--bs-dropdown-bg: #222;--bs-dropdown-border-color: #434343;--bs-dropdown-border-radius: 0.25rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.25rem - 1px);--bs-dropdown-divider-bg: #434343;--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: #fff;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-link-hover-bg: #375a7f;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #375a7f;--bs-dropdown-link-disabled-color: rgba(255, 255, 255, 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: #434343;--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: #434343;--bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #375a7f;--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: 2rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #00bc8c;--bs-nav-link-hover-color: #009670;--bs-nav-link-disabled-color: #6f6f6f;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(55,90,127,.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: #434343;--bs-nav-tabs-border-radius: 0.25rem;--bs-nav-tabs-link-hover-border-color: #434343 #434343 transparent;--bs-nav-tabs-link-active-color: #fff;--bs-nav-tabs-link-active-bg: #222;--bs-nav-tabs-link-active-border-color: #434343 #434343 transparent;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: #fff;--bs-nav-pills-link-active-bg: #375a7f}.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: 1rem;--bs-navbar-color: #dee2e6;--bs-navbar-hover-color: rgba(255, 255, 255, 0.8);--bs-navbar-disabled-color: rgba(222, 226, 230, 0.75);--bs-navbar-active-color: #fff;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #dee2e6;--bs-navbar-brand-hover-color: #fff;--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='%23dee2e6' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(222, 226, 230, 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: #dee2e6;--bs-navbar-hover-color: rgba(255, 255, 255, 0.8);--bs-navbar-disabled-color: rgba(222, 226, 230, 0.75);--bs-navbar-active-color: #fff;--bs-navbar-brand-color: #dee2e6;--bs-navbar-brand-hover-color: #fff;--bs-navbar-toggler-border-color: rgba(222, 226, 230, 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='%23dee2e6' 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='%23dee2e6' 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: #2d2d2d;--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: #fff;--bs-accordion-bg: #222;--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: #fff;--bs-accordion-btn-bg: #222;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%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='%23162433'%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: #9badbf;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #162433;--bs-accordion-active-bg: #d7dee5}.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='%23879cb2'%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='%23879cb2'%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.75rem;--bs-breadcrumb-padding-y: 0.375rem;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: #434343;--bs-breadcrumb-border-radius: 0.25rem;--bs-breadcrumb-divider-color: rgba(255, 255, 255, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(255, 255, 255, 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: #fff;--bs-pagination-bg: #00bc8c;--bs-pagination-border-width: 0;--bs-pagination-border-color: transparent;--bs-pagination-border-radius: 0.25rem;--bs-pagination-hover-color: #fff;--bs-pagination-hover-bg: #00efb2;--bs-pagination-hover-border-color: transparent;--bs-pagination-focus-color: #009670;--bs-pagination-focus-bg: #ebebeb;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #00efb2;--bs-pagination-active-border-color: transparent;--bs-pagination-disabled-color: #fff;--bs-pagination-disabled-bg: #007053;--bs-pagination-disabled-border-color: transparent;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(0*-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: #fff;--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: #434343;--bs-progress-border-radius: 0.25rem;--bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #375a7f;--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: #fff;--bs-list-group-bg: #2d2d2d;--bs-list-group-border-color: #434343;--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(255, 255, 255, 0.75);--bs-list-group-action-hover-color: #fff;--bs-list-group-action-hover-bg: #434343;--bs-list-group-action-active-color: #fff;--bs-list-group-action-active-bg: #222;--bs-list-group-disabled-color: rgba(255, 255, 255, 0.75);--bs-list-group-disabled-bg: #2d2d2d;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #375a7f;--bs-list-group-active-border-color: #375a7f;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: #fff;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%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.4;--bs-btn-close-hover-opacity: 1;--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 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: #434343;--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(255, 255, 255, 0.75);--bs-toast-header-bg: #2d2d2d;--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: #2d2d2d;--bs-modal-border-color: #434343;--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: #434343;--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: #434343;--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: #222;--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:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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: #2d2d2d;--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: #434343;--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: #fff;--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:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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='%23fff'%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='%23fff'%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: #fff;--bs-offcanvas-bg: #222;--bs-offcanvas-border-width: 1px;--bs-offcanvas-border-color: #434343;--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:#fff !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:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !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:#fff !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(54, 54, 54, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(54, 54, 54, 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(44, 72, 102, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 72, 102, 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(54, 54, 54, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(54, 54, 54, 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(0, 150, 112, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(0, 150, 112, 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(42, 122, 175, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 122, 175, 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(194, 125, 14, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(194, 125, 14, 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(185, 61, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(185, 61, 48, 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(89, 89, 89, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(89, 89, 89, 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(36, 36, 36, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(36, 36, 36, 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.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: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:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#fff}.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: #375a7f;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #375a7f;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #fff;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: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #e74c3c;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #fd7e14;--bslib-color-fg: #fff;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: #f39c12;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #f39c12;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #00bc8c;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #fff;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: #3498db;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #3498db;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #434343}.bg-default{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #375a7f}.bg-primary{--bslib-color-bg: #375a7f;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #434343}.bg-secondary{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #00bc8c}.bg-success{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #3498db}.bg-info{--bslib-color-bg: #3498db;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #f39c12}.bg-warning{--bslib-color-bg: #f39c12;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #e74c3c}.bg-danger{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #6f6f6f}.bg-light{--bslib-color-bg: #6f6f6f;--bslib-color-fg: #fff}.text-dark{--bslib-color-fg: #2d2d2d}.bg-dark{--bslib-color-bg: #2d2d2d;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4a3cad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4a3cad;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d5099;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4d5099;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7e4f84;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7e4f84;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d5464;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7d5464;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #866854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #866854;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #827453;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #827453;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #218184;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #218184;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2e8689;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2e8689;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #3673a4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #3673a4;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #532ec4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #532ec4;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #9a28a9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9a28a9;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9e4898;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #9e4898;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #3d55c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #3d55c9;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5246e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5246e9;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #594ca7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #594ca7;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9f40ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9f40ac;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #9f468c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9f468c;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #a4667b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #a4667b;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #4373ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #4373ac;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5764cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5764cb;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a14987;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a14987;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b840a1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b840a1;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #e8446c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #e8446c;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0585c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0585c;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec645b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec645b;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b708c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b708c;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a062ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a062ac;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a15257;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a15257;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b33485;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b33485;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b74871;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b74871;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #e7465c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #e7465c;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0602c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0602c;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec6c2b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec6c2b;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b795c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b795c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #977e60;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #977e60;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #9f6a7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #9f6a7c;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #ae703f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #ae703f;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f56444;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f56444;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f46a24;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #f46a24;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f98a13;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #f98a13;color:#fff}.bg-gradient-orange-green{--bslib-color-fg: #fff;--bslib-color-bg: #989744;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #989744;color:#fff}.bg-gradient-orange-teal{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #ad8864;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #ad8864;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a8823e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a8823e;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #bb646c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #bb646c;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #be7858;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #be7858;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ef7643;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ef7643;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ee7c23;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #ee7c23;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f79013;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f79013;color:#fff}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #92a943;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #92a943;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #9fae47;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9fae47;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a79a62;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a79a62;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #169587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #169587;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #2977b5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #2977b5;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #2c8ba1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #2c8ba1;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #5d8a8c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #5d8a8c;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #5c8f6c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #5c8f6c;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #fff;--bslib-color-bg: #65a35c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #65a35c;color:#fff}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #61af5b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #61af5b;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #fff;--bslib-color-bg: #0dc190;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #0dc190;color:#fff}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #15aeac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #15aeac;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #299d8d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #299d8d;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #709773;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #709773;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #74b762;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #74b762;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #fff;--bslib-color-bg: #13c493;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #13c493;color:#fff}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #28b5b2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #28b5b2;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #357fb6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #357fb6;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4862e4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4862e4;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4c76d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4c76d1;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7c74bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7c74bb;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #7c7a9b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7c7a9b;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #848e8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #848e8b;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #809a8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #809a8b;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #1fa6bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #1fa6bb;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2cacc0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2cacc0;color:#fff}.bg-blue{--bslib-color-bg: #375a7f;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #375a7f;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #fff;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: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #e74c3c;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #fd7e14;--bslib-color-fg: #fff;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: #f39c12;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #f39c12;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #00bc8c;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #fff;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: #3498db;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #3498db;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #434343}.bg-default{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #375a7f}.bg-primary{--bslib-color-bg: #375a7f;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #434343}.bg-secondary{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #00bc8c}.bg-success{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #3498db}.bg-info{--bslib-color-bg: #3498db;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #f39c12}.bg-warning{--bslib-color-bg: #f39c12;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #e74c3c}.bg-danger{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #6f6f6f}.bg-light{--bslib-color-bg: #6f6f6f;--bslib-color-fg: #fff}.text-dark{--bslib-color-fg: #2d2d2d}.bg-dark{--bslib-color-bg: #2d2d2d;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4a3cad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4a3cad;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d5099;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4d5099;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7e4f84;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7e4f84;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d5464;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7d5464;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #866854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #866854;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #827453;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #827453;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #218184;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #218184;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2e8689;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2e8689;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #3673a4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #3673a4;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #532ec4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #532ec4;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #9a28a9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9a28a9;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9e4898;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #9e4898;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #3d55c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #3d55c9;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5246e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5246e9;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #594ca7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #594ca7;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9f40ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9f40ac;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #9f468c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9f468c;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #a4667b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #a4667b;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #4373ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #4373ac;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5764cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5764cb;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a14987;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a14987;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b840a1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b840a1;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #e8446c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #e8446c;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0585c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0585c;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec645b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec645b;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b708c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b708c;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a062ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a062ac;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a15257;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a15257;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b33485;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b33485;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b74871;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b74871;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #e7465c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #e7465c;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0602c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0602c;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec6c2b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec6c2b;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b795c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b795c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #977e60;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #977e60;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #9f6a7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #9f6a7c;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #ae703f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #ae703f;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f56444;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f56444;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f46a24;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #f46a24;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f98a13;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #f98a13;color:#fff}.bg-gradient-orange-green{--bslib-color-fg: #fff;--bslib-color-bg: #989744;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #989744;color:#fff}.bg-gradient-orange-teal{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #ad8864;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #ad8864;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a8823e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a8823e;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #bb646c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #bb646c;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #be7858;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #be7858;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ef7643;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ef7643;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ee7c23;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #ee7c23;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f79013;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f79013;color:#fff}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #92a943;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #92a943;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #9fae47;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9fae47;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a79a62;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a79a62;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #169587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #169587;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #2977b5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #2977b5;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #2c8ba1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #2c8ba1;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #5d8a8c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #5d8a8c;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #5c8f6c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #5c8f6c;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #fff;--bslib-color-bg: #65a35c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #65a35c;color:#fff}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #61af5b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #61af5b;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #fff;--bslib-color-bg: #0dc190;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #0dc190;color:#fff}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #15aeac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #15aeac;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #299d8d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #299d8d;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #709773;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #709773;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #74b762;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #74b762;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #fff;--bslib-color-bg: #13c493;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #13c493;color:#fff}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #28b5b2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #28b5b2;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #357fb6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #357fb6;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4862e4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4862e4;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4c76d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4c76d1;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7c74bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7c74bb;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #7c7a9b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7c7a9b;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #848e8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #848e8b;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #809a8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #809a8b;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #1fa6bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #1fa6bb;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2cacc0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2cacc0;color:#fff}: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}.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)}.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)}}@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}}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)}}.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)}:root{--bslib-page-sidebar-title-bg: #375a7f;--bslib-page-sidebar-title-color: #fff}.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}}: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, #222);--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%)}.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:#fff}.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:#dee2e6}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fff}.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:#adadad}.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:#adadad;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#adadad}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#adadad}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(26,195,152,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#7a7a7a}.breadcrumb-item{line-height:1.2rem}div.sidebar-item-container{color:#adadad}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(26,195,152,.8)}div.sidebar-item-container.disabled{color:rgba(173,173,173,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#1ac398}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#222}@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 #434343}.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:#222;border-bottom:1px solid #434343}.quarto-banner nav.quarto-secondary-nav{background-color:#375a7f;color:#dee2e6;border-top:1px solid #434343}.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:#009670}.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:#222}body.nav-fixed{padding-top:82px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#8a8a8a}.nav-footer a{color:#8a8a8a}.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:#dee2e6;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:#adadad;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:#222;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:#222;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:#dee2e6;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#dee2e6;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #adb5bd;border-radius:.25rem;color:#2d2d2d;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(55,90,127,.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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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 #adb5bd 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:#2f2f2f;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:#375a7f}.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:#375a7f}.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:#2b4663}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#2d2d2d}.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:#fff}.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:#000}.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:#2d2d2d;color:#fff}.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:#2d2d2d;border-color:#adb5bd;color:#fff}.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 #adb5bd}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#dee2e6}.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:#adadad}.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(34,34,34,.65);width:90%;bottom:0;box-shadow:rgba(173,181,189,.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:#222;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:#222;border-bottom:1px solid #adb5bd;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:#fff;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(55,90,127,.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(255,255,255,.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:#375a7f !important}.quarto-dashboard .card.valuebox.bslib-card.bg-secondary{background-color:#434343 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-success{background-color:#00bc8c !important}.quarto-dashboard .card.valuebox.bslib-card.bg-info{background-color:#3498db !important}.quarto-dashboard .card.valuebox.bslib-card.bg-warning{background-color:#f39c12 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-danger{background-color:#e74c3c !important}.quarto-dashboard .card.valuebox.bslib-card.bg-light{background-color:#6f6f6f !important}.quarto-dashboard .card.valuebox.bslib-card.bg-dark{background-color:#2d2d2d !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 #4673a3;border-bottom:solid 1px #4673a3}.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:#436e9b}.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:#dee2e6}.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:#fff}.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:#fff}.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 silver;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:#222;border:none}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons .btn-secondary{background-color:#222;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:#fff}.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:#2d2d2d}.tableFloatingHeaderOriginal{background-color:#2d2d2d;position:sticky !important;top:0 !important}.dashboard-data-table{margin-top:-1px}.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}.listing-actions-group .form-select,.listing-actions-group .form-control{background-color:#222;color:#fff}.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 #6c757d 1px;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:#fff;text-decoration:none}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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 #6c757d 1px;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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#222;border:solid 1px #dee2e6;border-radius:.25rem;color:#fff;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#222}.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:#222}.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:#222;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:#222}.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:#222;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:#fff}.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}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}/*! + */@import"https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400&display=swap";:root,[data-bs-theme=light]{--bs-blue: #375a7f;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #e83e8c;--bs-red: #e74c3c;--bs-orange: #fd7e14;--bs-yellow: #f39c12;--bs-green: #00bc8c;--bs-teal: #20c997;--bs-cyan: #3498db;--bs-black: #000;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #ebebeb;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #444;--bs-gray-800: #343a40;--bs-gray-900: #222;--bs-default: #434343;--bs-primary: #375a7f;--bs-secondary: #434343;--bs-success: #00bc8c;--bs-info: #3498db;--bs-warning: #f39c12;--bs-danger: #e74c3c;--bs-light: #6f6f6f;--bs-dark: #2d2d2d;--bs-default-rgb: 67, 67, 67;--bs-primary-rgb: 55, 90, 127;--bs-secondary-rgb: 67, 67, 67;--bs-success-rgb: 0, 188, 140;--bs-info-rgb: 52, 152, 219;--bs-warning-rgb: 243, 156, 18;--bs-danger-rgb: 231, 76, 60;--bs-light-rgb: 111, 111, 111;--bs-dark-rgb: 45, 45, 45;--bs-primary-text-emphasis: #162433;--bs-secondary-text-emphasis: #1b1b1b;--bs-success-text-emphasis: #004b38;--bs-info-text-emphasis: #153d58;--bs-warning-text-emphasis: #613e07;--bs-danger-text-emphasis: #5c1e18;--bs-light-text-emphasis: #444;--bs-dark-text-emphasis: #444;--bs-primary-bg-subtle: #d7dee5;--bs-secondary-bg-subtle: #d9d9d9;--bs-success-bg-subtle: #ccf2e8;--bs-info-bg-subtle: #d6eaf8;--bs-warning-bg-subtle: #fdebd0;--bs-danger-bg-subtle: #fadbd8;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #afbdcc;--bs-secondary-border-subtle: #b4b4b4;--bs-success-border-subtle: #99e4d1;--bs-info-border-subtle: #aed6f1;--bs-warning-border-subtle: #fad7a0;--bs-danger-border-subtle: #f5b7b1;--bs-light-border-subtle: #ebebeb;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--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: 1rem;--bs-body-font-family: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #fff;--bs-body-color-rgb: 255, 255, 255;--bs-body-bg: #222;--bs-body-bg-rgb: 34, 34, 34;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(255, 255, 255, 0.75);--bs-secondary-color-rgb: 255, 255, 255;--bs-secondary-bg: #ebebeb;--bs-secondary-bg-rgb: 235, 235, 235;--bs-tertiary-color: rgba(255, 255, 255, 0.5);--bs-tertiary-color-rgb: 255, 255, 255;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #00bc8c;--bs-link-color-rgb: 0, 188, 140;--bs-link-decoration: underline;--bs-link-hover-color: #009670;--bs-link-hover-color-rgb: 0, 150, 112;--bs-code-color: #7d12ba;--bs-highlight-bg: #fdebd0;--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(55, 90, 127, 0.25);--bs-form-valid-color: #00bc8c;--bs-form-valid-border-color: #00bc8c;--bs-form-invalid-color: #e74c3c;--bs-form-invalid-border-color: #e74c3c}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #222;--bs-body-bg-rgb: 34, 34, 34;--bs-emphasis-color: #fff;--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: #2b2e31;--bs-tertiary-bg-rgb: 43, 46, 49;--bs-primary-text-emphasis: #879cb2;--bs-secondary-text-emphasis: #8e8e8e;--bs-success-text-emphasis: #66d7ba;--bs-info-text-emphasis: #85c1e9;--bs-warning-text-emphasis: #f8c471;--bs-danger-text-emphasis: #f1948a;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #0b1219;--bs-secondary-bg-subtle: #0d0d0d;--bs-success-bg-subtle: #00261c;--bs-info-bg-subtle: #0a1e2c;--bs-warning-bg-subtle: #311f04;--bs-danger-bg-subtle: #2e0f0c;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #21364c;--bs-secondary-border-subtle: #282828;--bs-success-border-subtle: #007154;--bs-info-border-subtle: #1f5b83;--bs-warning-border-subtle: #925e0b;--bs-danger-border-subtle: #8b2e24;--bs-light-border-subtle: #444;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #879cb2;--bs-link-hover-color: #9fb0c1;--bs-link-color-rgb: 135, 156, 178;--bs-link-hover-color-rgb: 159, 176, 193;--bs-code-color: white;--bs-border-color: #444;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #66d7ba;--bs-form-valid-border-color: #66d7ba;--bs-form-invalid-color: #f1948a;--bs-form-invalid-border-color: #f1948a}*,*::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 #ebebeb}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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:inherit;background-color:#f8f9fa;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:#f8f9fa;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#222;background-color:#fff;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(255,255,255,.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:#222;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(255,255,255,.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: #fff;--bs-table-bg: #222;--bs-table-border-color: #434343;--bs-table-accent-bg: transparent;--bs-table-striped-color: #fff;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #fff;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #fff;--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 #fff}.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: #fff;--bs-table-bg: #375a7f;--bs-table-border-color: #4b6b8c;--bs-table-striped-bg: #416285;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b6b8c;--bs-table-active-color: #fff;--bs-table-hover-bg: #466689;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #fff;--bs-table-bg: #434343;--bs-table-border-color: #565656;--bs-table-striped-bg: #4c4c4c;--bs-table-striped-color: #fff;--bs-table-active-bg: #565656;--bs-table-active-color: #fff;--bs-table-hover-bg: #515151;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #fff;--bs-table-bg: #00bc8c;--bs-table-border-color: #1ac398;--bs-table-striped-bg: #0dbf92;--bs-table-striped-color: #fff;--bs-table-active-bg: #1ac398;--bs-table-active-color: #fff;--bs-table-hover-bg: #13c195;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #fff;--bs-table-bg: #3498db;--bs-table-border-color: #48a2df;--bs-table-striped-bg: #3e9ddd;--bs-table-striped-color: #fff;--bs-table-active-bg: #48a2df;--bs-table-active-color: #fff;--bs-table-hover-bg: #43a0de;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #fff;--bs-table-bg: #f39c12;--bs-table-border-color: #f4a62a;--bs-table-striped-bg: #f4a11e;--bs-table-striped-color: #fff;--bs-table-active-bg: #f4a62a;--bs-table-active-color: #fff;--bs-table-hover-bg: #f4a324;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #fff;--bs-table-bg: #e74c3c;--bs-table-border-color: #e95e50;--bs-table-striped-bg: #e85546;--bs-table-striped-color: #fff;--bs-table-active-bg: #e95e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #e9594b;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #fff;--bs-table-bg: #6f6f6f;--bs-table-border-color: #7d7d7d;--bs-table-striped-bg: #767676;--bs-table-striped-color: #fff;--bs-table-active-bg: #7d7d7d;--bs-table-active-color: #fff;--bs-table-hover-bg: #7a7a7a;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #2d2d2d;--bs-table-border-color: #424242;--bs-table-striped-bg: #383838;--bs-table-striped-color: #fff;--bs-table-active-bg: #424242;--bs-table-active-color: #fff;--bs-table-hover-bg: #3d3d3d;--bs-table-hover-color: #fff;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(255,255,255,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#2d2d2d;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-clip:padding-box;border:1px solid #adb5bd;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:#2d2d2d;background-color:#fff;border-color:#9badbf;outline:0;box-shadow:0 0 0 .25rem rgba(55,90,127,.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:#595959;opacity:1}.form-control:disabled{background-color:#ebebeb;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#6f6f6f;background-color:#434343;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:#363636}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#fff;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:#2d2d2d;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 #adb5bd;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:#9badbf;outline:0;box-shadow:0 0 0 .25rem rgba(55,90,127,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{color:#595959;background-color:#ebebeb}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #2d2d2d}.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: #fff;width:1em;height:1em;margin-top:.25em;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:none;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:#9badbf;outline:0;box-shadow:0 0 0 .25rem rgba(55,90,127,.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:#375a7f;border-color:#375a7f}.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='%23fff' 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='%23fff'/%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:#375a7f;border-color:#375a7f;--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='%23fff' 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='%239badbf'/%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='%23fff'/%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 #222,0 0 0 .25rem rgba(55,90,127,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #222,0 0 0 .25rem rgba(55,90,127,.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:#375a7f;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:#c3ced9}.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:#375a7f;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:#c3ced9}.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(255,255,255,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(255,255,255,.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:#ebebeb}.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:#6f6f6f;text-align:center;white-space:nowrap;background-color:#434343;border:1px solid #adb5bd;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:#00bc8c}.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:#00bc8c;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:#00bc8c;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='%2300bc8c' 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:#00bc8c;box-shadow:0 0 0 .25rem rgba(0,188,140,.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:#00bc8c}.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='%2300bc8c' 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:#00bc8c;box-shadow:0 0 0 .25rem rgba(0,188,140,.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:#00bc8c}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#00bc8c}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(0,188,140,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#00bc8c}.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:#e74c3c}.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:#e74c3c;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:#e74c3c;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='%23e74c3c'%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='%23e74c3c' 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:#e74c3c;box-shadow:0 0 0 .25rem rgba(231,76,60,.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:#e74c3c}.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='%23e74c3c'%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='%23e74c3c' 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:#e74c3c;box-shadow:0 0 0 .25rem rgba(231,76,60,.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:#e74c3c}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#e74c3c}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(231,76,60,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#e74c3c}.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: #fff;--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: #fff;--bs-btn-bg: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #393939;--bs-btn-hover-border-color: #363636;--bs-btn-focus-shadow-rgb: 95, 95, 95;--bs-btn-active-color: #fff;--bs-btn-active-bg: #363636;--bs-btn-active-border-color: #323232;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #434343;--bs-btn-disabled-border-color: #434343}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #375a7f;--bs-btn-border-color: #375a7f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2f4d6c;--bs-btn-hover-border-color: #2c4866;--bs-btn-focus-shadow-rgb: 85, 115, 146;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2c4866;--bs-btn-active-border-color: #29445f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #375a7f;--bs-btn-disabled-border-color: #375a7f}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #393939;--bs-btn-hover-border-color: #363636;--bs-btn-focus-shadow-rgb: 95, 95, 95;--bs-btn-active-color: #fff;--bs-btn-active-bg: #363636;--bs-btn-active-border-color: #323232;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #434343;--bs-btn-disabled-border-color: #434343}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #00bc8c;--bs-btn-border-color: #00bc8c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #00a077;--bs-btn-hover-border-color: #009670;--bs-btn-focus-shadow-rgb: 38, 198, 157;--bs-btn-active-color: #fff;--bs-btn-active-bg: #009670;--bs-btn-active-border-color: #008d69;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #00bc8c;--bs-btn-disabled-border-color: #00bc8c}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #3498db;--bs-btn-border-color: #3498db;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c81ba;--bs-btn-hover-border-color: #2a7aaf;--bs-btn-focus-shadow-rgb: 82, 167, 224;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a7aaf;--bs-btn-active-border-color: #2772a4;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3498db;--bs-btn-disabled-border-color: #3498db}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #f39c12;--bs-btn-border-color: #f39c12;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #cf850f;--bs-btn-hover-border-color: #c27d0e;--bs-btn-focus-shadow-rgb: 245, 171, 54;--bs-btn-active-color: #fff;--bs-btn-active-bg: #c27d0e;--bs-btn-active-border-color: #b6750e;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #f39c12;--bs-btn-disabled-border-color: #f39c12}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #e74c3c;--bs-btn-border-color: #e74c3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #c44133;--bs-btn-hover-border-color: #b93d30;--bs-btn-focus-shadow-rgb: 235, 103, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #b93d30;--bs-btn-active-border-color: #ad392d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #e74c3c;--bs-btn-disabled-border-color: #e74c3c}.btn-light{--bs-btn-color: #fff;--bs-btn-bg: #6f6f6f;--bs-btn-border-color: #6f6f6f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #5e5e5e;--bs-btn-hover-border-color: #595959;--bs-btn-focus-shadow-rgb: 133, 133, 133;--bs-btn-active-color: #fff;--bs-btn-active-bg: #595959;--bs-btn-active-border-color: #535353;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #6f6f6f;--bs-btn-disabled-border-color: #6f6f6f}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #2d2d2d;--bs-btn-border-color: #2d2d2d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #4d4d4d;--bs-btn-hover-border-color: #424242;--bs-btn-focus-shadow-rgb: 77, 77, 77;--bs-btn-active-color: #fff;--bs-btn-active-bg: #575757;--bs-btn-active-border-color: #424242;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2d2d2d;--bs-btn-disabled-border-color: #2d2d2d}.btn-outline-default{--bs-btn-color: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #434343;--bs-btn-hover-border-color: #434343;--bs-btn-focus-shadow-rgb: 67, 67, 67;--bs-btn-active-color: #fff;--bs-btn-active-bg: #434343;--bs-btn-active-border-color: #434343;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #434343;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #434343;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #375a7f;--bs-btn-border-color: #375a7f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #375a7f;--bs-btn-hover-border-color: #375a7f;--bs-btn-focus-shadow-rgb: 55, 90, 127;--bs-btn-active-color: #fff;--bs-btn-active-bg: #375a7f;--bs-btn-active-border-color: #375a7f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #375a7f;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #375a7f;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #434343;--bs-btn-hover-border-color: #434343;--bs-btn-focus-shadow-rgb: 67, 67, 67;--bs-btn-active-color: #fff;--bs-btn-active-bg: #434343;--bs-btn-active-border-color: #434343;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #434343;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #434343;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #00bc8c;--bs-btn-border-color: #00bc8c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #00bc8c;--bs-btn-hover-border-color: #00bc8c;--bs-btn-focus-shadow-rgb: 0, 188, 140;--bs-btn-active-color: #fff;--bs-btn-active-bg: #00bc8c;--bs-btn-active-border-color: #00bc8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #00bc8c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #00bc8c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #3498db;--bs-btn-border-color: #3498db;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3498db;--bs-btn-hover-border-color: #3498db;--bs-btn-focus-shadow-rgb: 52, 152, 219;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3498db;--bs-btn-active-border-color: #3498db;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3498db;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3498db;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #f39c12;--bs-btn-border-color: #f39c12;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #f39c12;--bs-btn-hover-border-color: #f39c12;--bs-btn-focus-shadow-rgb: 243, 156, 18;--bs-btn-active-color: #fff;--bs-btn-active-bg: #f39c12;--bs-btn-active-border-color: #f39c12;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f39c12;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f39c12;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #e74c3c;--bs-btn-border-color: #e74c3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #e74c3c;--bs-btn-hover-border-color: #e74c3c;--bs-btn-focus-shadow-rgb: 231, 76, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #e74c3c;--bs-btn-active-border-color: #e74c3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #e74c3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #e74c3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #6f6f6f;--bs-btn-border-color: #6f6f6f;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #6f6f6f;--bs-btn-hover-border-color: #6f6f6f;--bs-btn-focus-shadow-rgb: 111, 111, 111;--bs-btn-active-color: #fff;--bs-btn-active-bg: #6f6f6f;--bs-btn-active-border-color: #6f6f6f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #6f6f6f;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #6f6f6f;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #2d2d2d;--bs-btn-border-color: #2d2d2d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2d2d2d;--bs-btn-hover-border-color: #2d2d2d;--bs-btn-focus-shadow-rgb: 45, 45, 45;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2d2d2d;--bs-btn-active-border-color: #2d2d2d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2d2d2d;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2d2d2d;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #00bc8c;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #009670;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #009670;--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, 198, 157;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: #fff;--bs-dropdown-bg: #222;--bs-dropdown-border-color: #434343;--bs-dropdown-border-radius: 0.25rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.25rem - 1px);--bs-dropdown-divider-bg: #434343;--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: #fff;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-link-hover-bg: #375a7f;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #375a7f;--bs-dropdown-link-disabled-color: rgba(255, 255, 255, 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: #434343;--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: #434343;--bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #375a7f;--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: 2rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #00bc8c;--bs-nav-link-hover-color: #009670;--bs-nav-link-disabled-color: #6f6f6f;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(55,90,127,.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: #434343;--bs-nav-tabs-border-radius: 0.25rem;--bs-nav-tabs-link-hover-border-color: #434343 #434343 transparent;--bs-nav-tabs-link-active-color: #fff;--bs-nav-tabs-link-active-bg: #222;--bs-nav-tabs-link-active-border-color: #434343 #434343 transparent;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: #fff;--bs-nav-pills-link-active-bg: #375a7f}.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: 1rem;--bs-navbar-color: #dee2e6;--bs-navbar-hover-color: rgba(255, 255, 255, 0.8);--bs-navbar-disabled-color: rgba(222, 226, 230, 0.75);--bs-navbar-active-color: #fff;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #dee2e6;--bs-navbar-brand-hover-color: #fff;--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='%23dee2e6' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(222, 226, 230, 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: #dee2e6;--bs-navbar-hover-color: rgba(255, 255, 255, 0.8);--bs-navbar-disabled-color: rgba(222, 226, 230, 0.75);--bs-navbar-active-color: #fff;--bs-navbar-brand-color: #dee2e6;--bs-navbar-brand-hover-color: #fff;--bs-navbar-toggler-border-color: rgba(222, 226, 230, 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='%23dee2e6' 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='%23dee2e6' 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: #2d2d2d;--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: #fff;--bs-accordion-bg: #222;--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: #fff;--bs-accordion-btn-bg: #222;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%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='%23162433'%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: #9badbf;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #162433;--bs-accordion-active-bg: #d7dee5}.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='%23879cb2'%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='%23879cb2'%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.75rem;--bs-breadcrumb-padding-y: 0.375rem;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: #434343;--bs-breadcrumb-border-radius: 0.25rem;--bs-breadcrumb-divider-color: rgba(255, 255, 255, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(255, 255, 255, 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: #fff;--bs-pagination-bg: #00bc8c;--bs-pagination-border-width: 0;--bs-pagination-border-color: transparent;--bs-pagination-border-radius: 0.25rem;--bs-pagination-hover-color: #fff;--bs-pagination-hover-bg: #00efb2;--bs-pagination-hover-border-color: transparent;--bs-pagination-focus-color: #009670;--bs-pagination-focus-bg: #ebebeb;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #00efb2;--bs-pagination-active-border-color: transparent;--bs-pagination-disabled-color: #fff;--bs-pagination-disabled-bg: #007053;--bs-pagination-disabled-border-color: transparent;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(0*-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: #fff;--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: #434343;--bs-progress-border-radius: 0.25rem;--bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #375a7f;--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: #fff;--bs-list-group-bg: #2d2d2d;--bs-list-group-border-color: #434343;--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(255, 255, 255, 0.75);--bs-list-group-action-hover-color: #fff;--bs-list-group-action-hover-bg: #434343;--bs-list-group-action-active-color: #fff;--bs-list-group-action-active-bg: #222;--bs-list-group-disabled-color: rgba(255, 255, 255, 0.75);--bs-list-group-disabled-bg: #2d2d2d;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #375a7f;--bs-list-group-active-border-color: #375a7f;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: #fff;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%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.4;--bs-btn-close-hover-opacity: 1;--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 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: #434343;--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(255, 255, 255, 0.75);--bs-toast-header-bg: #2d2d2d;--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: #2d2d2d;--bs-modal-border-color: #434343;--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: #434343;--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: #434343;--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: #222;--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:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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: #2d2d2d;--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: #434343;--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: #fff;--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:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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='%23fff'%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='%23fff'%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: #fff;--bs-offcanvas-bg: #222;--bs-offcanvas-border-width: 1px;--bs-offcanvas-border-color: #434343;--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:#fff !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:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !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:#fff !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(54, 54, 54, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(54, 54, 54, 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(44, 72, 102, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 72, 102, 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(54, 54, 54, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(54, 54, 54, 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(0, 150, 112, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(0, 150, 112, 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(42, 122, 175, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 122, 175, 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(194, 125, 14, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(194, 125, 14, 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(185, 61, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(185, 61, 48, 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(89, 89, 89, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(89, 89, 89, 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(36, 36, 36, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(36, 36, 36, 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.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: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:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#fff}.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: #375a7f;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #375a7f;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #fff;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: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #e74c3c;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #fd7e14;--bslib-color-fg: #fff;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: #f39c12;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #f39c12;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #00bc8c;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #fff;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: #3498db;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #3498db;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #434343}.bg-default{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #375a7f}.bg-primary{--bslib-color-bg: #375a7f;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #434343}.bg-secondary{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #00bc8c}.bg-success{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #3498db}.bg-info{--bslib-color-bg: #3498db;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #f39c12}.bg-warning{--bslib-color-bg: #f39c12;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #e74c3c}.bg-danger{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #6f6f6f}.bg-light{--bslib-color-bg: #6f6f6f;--bslib-color-fg: #fff}.text-dark{--bslib-color-fg: #2d2d2d}.bg-dark{--bslib-color-bg: #2d2d2d;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4a3cad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4a3cad;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d5099;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4d5099;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7e4f84;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7e4f84;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d5464;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7d5464;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #866854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #866854;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #827453;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #827453;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #218184;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #218184;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2e8689;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2e8689;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #3673a4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #3673a4;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #532ec4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #532ec4;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #9a28a9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9a28a9;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9e4898;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #9e4898;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #3d55c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #3d55c9;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5246e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5246e9;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #594ca7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #594ca7;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9f40ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9f40ac;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #9f468c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9f468c;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #a4667b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #a4667b;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #4373ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #4373ac;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5764cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5764cb;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a14987;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a14987;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b840a1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b840a1;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #e8446c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #e8446c;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0585c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0585c;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec645b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec645b;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b708c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b708c;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a062ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a062ac;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a15257;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a15257;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b33485;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b33485;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b74871;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b74871;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #e7465c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #e7465c;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0602c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0602c;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec6c2b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec6c2b;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b795c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b795c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #977e60;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #977e60;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #9f6a7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #9f6a7c;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #ae703f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #ae703f;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f56444;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f56444;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f46a24;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #f46a24;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f98a13;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #f98a13;color:#fff}.bg-gradient-orange-green{--bslib-color-fg: #fff;--bslib-color-bg: #989744;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #989744;color:#fff}.bg-gradient-orange-teal{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #ad8864;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #ad8864;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a8823e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a8823e;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #bb646c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #bb646c;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #be7858;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #be7858;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ef7643;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ef7643;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ee7c23;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #ee7c23;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f79013;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f79013;color:#fff}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #92a943;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #92a943;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #9fae47;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9fae47;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a79a62;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a79a62;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #169587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #169587;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #2977b5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #2977b5;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #2c8ba1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #2c8ba1;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #5d8a8c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #5d8a8c;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #5c8f6c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #5c8f6c;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #fff;--bslib-color-bg: #65a35c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #65a35c;color:#fff}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #61af5b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #61af5b;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #fff;--bslib-color-bg: #0dc190;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #0dc190;color:#fff}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #15aeac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #15aeac;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #299d8d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #299d8d;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #709773;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #709773;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #74b762;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #74b762;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #fff;--bslib-color-bg: #13c493;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #13c493;color:#fff}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #28b5b2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #28b5b2;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #357fb6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #357fb6;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4862e4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4862e4;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4c76d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4c76d1;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7c74bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7c74bb;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #7c7a9b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7c7a9b;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #848e8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #848e8b;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #809a8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #809a8b;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #1fa6bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #1fa6bb;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2cacc0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2cacc0;color:#fff}.bg-blue{--bslib-color-bg: #375a7f;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #375a7f;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #fff;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: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #e74c3c;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #fd7e14;--bslib-color-fg: #fff;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: #f39c12;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #f39c12;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #00bc8c;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #fff;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: #3498db;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #3498db;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #434343}.bg-default{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #375a7f}.bg-primary{--bslib-color-bg: #375a7f;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #434343}.bg-secondary{--bslib-color-bg: #434343;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #00bc8c}.bg-success{--bslib-color-bg: #00bc8c;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #3498db}.bg-info{--bslib-color-bg: #3498db;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #f39c12}.bg-warning{--bslib-color-bg: #f39c12;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #e74c3c}.bg-danger{--bslib-color-bg: #e74c3c;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #6f6f6f}.bg-light{--bslib-color-bg: #6f6f6f;--bslib-color-fg: #fff}.text-dark{--bslib-color-fg: #2d2d2d}.bg-dark{--bslib-color-bg: #2d2d2d;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4a3cad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4a3cad;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d5099;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4d5099;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7e4f84;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7e4f84;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d5464;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7d5464;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #866854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #866854;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #827453;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #827453;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #218184;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #218184;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2e8689;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2e8689;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #3673a4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #375a7f var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #3673a4;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #532ec4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #532ec4;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #9a28a9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9a28a9;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9e4898;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #9e4898;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #3d55c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #3d55c9;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5246e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5246e9;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #594ca7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #594ca7;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #9f40ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9f40ac;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #9f468c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #9f468c;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #a4667b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #a4667b;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #4373ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #4373ac;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #5764cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #5764cb;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a14987;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a14987;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b840a1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b840a1;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #e8446c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #e8446c;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0585c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0585c;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec645b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec645b;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b708c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b708c;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a062ac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a062ac;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a15257;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a15257;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b33485;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b33485;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b74871;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b74871;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #e7465c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #e7465c;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f0602c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f0602c;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ec6c2b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #ec6c2b;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #8b795c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #8b795c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #977e60;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #977e60;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #9f6a7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e74c3c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #9f6a7c;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #ae703f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #ae703f;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f56444;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f56444;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f46a24;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #f46a24;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f98a13;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #f98a13;color:#fff}.bg-gradient-orange-green{--bslib-color-fg: #fff;--bslib-color-bg: #989744;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #989744;color:#fff}.bg-gradient-orange-teal{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #ad8864;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #ad8864;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a8823e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #a8823e;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #bb646c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #bb646c;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #be7858;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #be7858;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ef7643;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ef7643;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ee7c23;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #ee7c23;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f79013;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #f79013;color:#fff}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #92a943;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #92a943;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #9fae47;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9fae47;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #a79a62;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f39c12 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #a79a62;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #169587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #169587;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #2977b5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #2977b5;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #2c8ba1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #2c8ba1;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #5d8a8c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #5d8a8c;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #5c8f6c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #5c8f6c;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #fff;--bslib-color-bg: #65a35c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #65a35c;color:#fff}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #61af5b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #61af5b;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #fff;--bslib-color-bg: #0dc190;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #0dc190;color:#fff}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #15aeac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #00bc8c var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #15aeac;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #299d8d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #299d8d;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #709773;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #709773;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #74b762;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #74b762;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #fff;--bslib-color-bg: #13c493;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #13c493;color:#fff}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #28b5b2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3498db var(--bg-gradient-end, 180%)) #28b5b2;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #357fb6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #375a7f var(--bg-gradient-end, 180%)) #357fb6;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4862e4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4862e4;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4c76d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4c76d1;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7c74bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7c74bb;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #7c7a9b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #e74c3c var(--bg-gradient-end, 180%)) #7c7a9b;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #848e8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #848e8b;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #809a8b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #f39c12 var(--bg-gradient-end, 180%)) #809a8b;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #1fa6bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #00bc8c var(--bg-gradient-end, 180%)) #1fa6bb;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #2cacc0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3498db var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #2cacc0;color:#fff}: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}.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)}.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)}}@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}}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)}}.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)}:root{--bslib-page-sidebar-title-bg: #375a7f;--bslib-page-sidebar-title-color: #fff}.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}}: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, #222);--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%)}.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:#fff}.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-collapse .quarto-navbar-tools{margin-left:.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:#dee2e6}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fff}.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:#adadad}.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:#adadad;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#adadad}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#adadad}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(26,195,152,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#7a7a7a}.breadcrumb-item{line-height:1.2rem}div.sidebar-item-container{color:#adadad}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(26,195,152,.8)}div.sidebar-item-container.disabled{color:rgba(173,173,173,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#1ac398}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#222}@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 #434343}.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:#222;border-bottom:1px solid #434343}.quarto-banner nav.quarto-secondary-nav{background-color:#375a7f;color:#dee2e6;border-top:1px solid #434343}.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(min-width: 992px){#quarto-sidebar-glass{display:none}}@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:#009670}.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:#222}body.nav-fixed{padding-top:82px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#8a8a8a}.nav-footer a{color:#8a8a8a}.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}@media(min-width: 768px){.nav-footer-left{flex:1 1 0px;text-align:left}}@media(max-width: 575.98px){.nav-footer-left{margin-bottom:1em;flex:100%}}@media(min-width: 768px){.nav-footer-right{flex:1 1 0px;text-align:right}}@media(max-width: 575.98px){.nav-footer-right{margin-bottom:1em;flex:100%}}.nav-footer-center{text-align:center;min-height:3em}@media(min-width: 768px){.nav-footer-center{flex:1 1 0px}}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-bottom:1em;flex:100%}}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em;order:10}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#dee2e6;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:#adadad;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:#222;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)}#quarto-announcement{padding:.5em;display:flex;justify-content:space-between;margin-bottom:0;font-size:.9em}#quarto-announcement .quarto-announcement-content{margin-right:auto}#quarto-announcement .quarto-announcement-content p{margin-bottom:0}#quarto-announcement .quarto-announcement-icon{margin-right:.5em;font-size:1.2em;margin-top:-0.15em}#quarto-announcement .quarto-announcement-action{cursor:pointer}.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:#222;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{order:900}}@media(min-width: 992px){.navbar .quarto-navbar-tools.tools-end{margin-left:auto !important}}@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:#dee2e6;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#dee2e6;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #adb5bd;border-radius:.25rem;color:#2d2d2d;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(55,90,127,.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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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:#2d2d2d;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 #adb5bd 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:#2f2f2f;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:#375a7f}.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:#375a7f}.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:#2b4663}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#2d2d2d}.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:#fff}.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:#000}.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:#2d2d2d;color:#fff}.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:#2d2d2d;border-color:#adb5bd;color:#fff}.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 #adb5bd}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#dee2e6}.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:#adadad}.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(34,34,34,.65);width:90%;bottom:0;box-shadow:rgba(173,181,189,.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:#222;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:#222;border-bottom:1px solid #adb5bd;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:#fff;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(55,90,127,.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(255,255,255,.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:#375a7f !important}.quarto-dashboard .card.valuebox.bslib-card.bg-secondary{background-color:#434343 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-success{background-color:#00bc8c !important}.quarto-dashboard .card.valuebox.bslib-card.bg-info{background-color:#3498db !important}.quarto-dashboard .card.valuebox.bslib-card.bg-warning{background-color:#f39c12 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-danger{background-color:#e74c3c !important}.quarto-dashboard .card.valuebox.bslib-card.bg-light{background-color:#6f6f6f !important}.quarto-dashboard .card.valuebox.bslib-card.bg-dark{background-color:#2d2d2d !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 #4673a3;border-bottom:solid 1px #4673a3}.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:#436e9b}.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:#dee2e6}.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:#fff}.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:#fff}.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 silver;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:4}@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:#222;border:none}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons .btn-secondary{background-color:#222;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:#fff}.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:#2d2d2d}.tableFloatingHeaderOriginal{background-color:#2d2d2d;position:sticky !important;top:0 !important}.dashboard-data-table{margin-top:-1px}div.value-box-area span.observablehq--number{font-size:calc(clamp(.1em,15cqw,5em)*1.25);line-height:1.2;color:inherit;font-family:var(--bs-body-font-family)}.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}.listing-actions-group .form-select,.listing-actions-group .form-control{background-color:#222;color:#fff}.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 #6c757d 1px;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:#fff;text-decoration:none}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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 #6c757d 1px;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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#fff;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:#00bc8c}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:#222;border:solid 1px #dee2e6;border-radius:.25rem;color:#fff;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#222}.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:#222}.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:#222;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:#222}.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:#222;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:#fff}.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}dd code:not(.sourceCode),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}.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 * * 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: #222;--quarto-body-color: #fff;--quarto-text-muted: #6c757d;--quarto-border-color: #434343;--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: #222;--mermaid-edge-color: #434343;--mermaid-node-fg-color: #fff;--mermaid-fg-color: #fff;--mermaid-fg-color--lighter: white;--mermaid-fg-color--lightest: white;--mermaid-font-family: Lato, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #222;--mermaid-label-fg-color: #375a7f;--mermaid-node-bg-color: rgba(55, 90, 127, 0.1);--mermaid-node-fg-color: #fff}@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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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:#222}.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:#222}.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:#222}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-page table{background:#222}.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:#222}.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:#222}.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:#222}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-left table{background:#222}.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:#222}#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:#222}.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:#222}.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:#222}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#222}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#222}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#222}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#6f6f6f;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 #434343;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:#bfbfbf}.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:#bfbfbf}.quarto-layout-cell[data-ref-parent] caption{color:#bfbfbf}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#bfbfbf;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:#434343 1px solid;border-right:#434343 1px solid;border-bottom:#434343 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(67,67,67,.65);border:1px solid rgba(67,67,67,.65);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:#bfbfbf}.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:#f8f9fa;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:#00bc8c}.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:#00bc8c}.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 #ebebeb;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 #00bc8c;color:#00bc8c !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#00bc8c !important}kbd,.kbd{color:#fff;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#434343}.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 #434343;border-top:1px solid #434343;border-bottom:1px solid #434343}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #434343;border-top:1px solid #434343;border-bottom:1px solid #434343}.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:#375a7f}div.callout-note.callout-style-default>.callout-header{background-color:#111b26}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:#00bc8c}div.callout-tip.callout-style-default>.callout-header{background-color:#00382a}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:#f39c12}div.callout-warning.callout-style-default>.callout-header{background-color:#492f05}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:#4c2606}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:#e74c3c}div.callout-important.callout-style-default>.callout-header{background-color:#451712}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:#375a7f;color:#dee2e6}.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:#272727}#quarto-content .quarto-sidebar-toggle-title{color:#fff}.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:#222;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:#222;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: #d9d9d9;--bs-btn-bg: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #d9d9d9;--bs-btn-hover-bg: #5f5f5f;--bs-btn-hover-border-color: #565656;--bs-btn-focus-shadow-rgb: 90, 90, 90;--bs-btn-active-color: #fff;--bs-btn-active-bg: dimgray;--bs-btn-active-border-color: #565656;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #434343;--bs-btn-disabled-border-color: #434343}nav.quarto-secondary-nav.color-navbar{background-color:#375a7f;color:#dee2e6}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#dee2e6}@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}/*! dark */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:#222}.code-annotation-gutter{background-color:rgba(67,67,67,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#e6e6e6;border:solid #e6e6e6 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:#222;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:#434343;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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:#222}.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:#222}.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:#222}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#222}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#222}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#222}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#6f6f6f;z-index:998;opacity:.999;margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table{border-top:1px solid #fff;border-bottom:1px solid #fff}.table>thead{border-top-width:0;border-bottom:1px solid #fff}.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}.blockquote-footer{color:#595959}.form-floating>label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label{color:#595959}.nav-tabs .nav-link,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover,.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-pills .nav-link,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover,.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover{color:#fff}.breadcrumb a{color:#fff}.pagination a:hover{text-decoration:none}.alert{color:#fff;border:none}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert-default{background-color:#434343}.alert-primary{background-color:#375a7f}.alert-secondary{background-color:#434343}.alert-success{background-color:#00bc8c}.alert-info{background-color:#3498db}.alert-warning{background-color:#f39c12}.alert-danger{background-color:#e74c3c}.alert-light{background-color:#6f6f6f}.alert-dark{background-color:#2d2d2d}.tooltip{--bs-tooltip-bg: var(--bs-tertiary-bg);--bs-tooltip-color: var(--bs-emphasis-color)}.dataTables_wrapper{position:relative;clear:both;color:#c3c3c3}*[style*="color: black"]{color:#6d96c0 !important}*[style*="color: darkgreen"]{color:#38c538 !important}.flextable-shadow-host{background-color:#fff !important}.quarto-title-banner{margin-bottom:1em;color:#dee2e6;background:#375a7f}.quarto-title-banner a{color:#dee2e6}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#dee2e6}.quarto-title-banner .code-tools-button{color:#a4afba}.quarto-title-banner .code-tools-button:hover{color:#dee2e6}.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} +*/.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: #222;--quarto-body-color: #fff;--quarto-text-muted: #6c757d;--quarto-border-color: #434343;--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: #222;--mermaid-edge-color: #434343;--mermaid-node-fg-color: #fff;--mermaid-fg-color: #fff;--mermaid-fg-color--lighter: white;--mermaid-fg-color--lightest: white;--mermaid-font-family: Lato, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #222;--mermaid-label-fg-color: #375a7f;--mermaid-node-bg-color: rgba(55, 90, 127, 0.1);--mermaid-node-fg-color: #fff}@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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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:#222}.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:#222}.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:#222}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-page table{background:#222}.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:#222}.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:#222}.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:#222}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-left table{background:#222}.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:#222}#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:#222}.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:#222}.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:#222}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#222}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#222}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#222}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#6f6f6f;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 #434343;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:#bfbfbf}.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:#bfbfbf}.quarto-layout-cell[data-ref-parent] caption{color:#bfbfbf}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#bfbfbf;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:#434343 1px solid;border-right:#434343 1px solid;border-bottom:#434343 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(67,67,67,.65);border:1px solid rgba(67,67,67,.65);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:#bfbfbf}.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:#f8f9fa;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:#00bc8c}.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:#00bc8c}.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 #ebebeb;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 #00bc8c;color:#00bc8c !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#00bc8c !important}kbd,.kbd{color:#fff;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#434343}.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 #434343;border-top:1px solid #434343;border-bottom:1px solid #434343}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #434343;border-top:1px solid #434343;border-bottom:1px solid #434343}.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:#375a7f}div.callout-note.callout-style-default>.callout-header{background-color:#111b26}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:#00bc8c}div.callout-tip.callout-style-default>.callout-header{background-color:#00382a}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:#f39c12}div.callout-warning.callout-style-default>.callout-header{background-color:#492f05}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:#4c2606}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:#e74c3c}div.callout-important.callout-style-default>.callout-header{background-color:#451712}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:#375a7f;color:#dee2e6}.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:#272727}#quarto-content .quarto-sidebar-toggle-title{color:#fff}.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:#222;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:#222;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: #d9d9d9;--bs-btn-bg: #434343;--bs-btn-border-color: #434343;--bs-btn-hover-color: #d9d9d9;--bs-btn-hover-bg: #5f5f5f;--bs-btn-hover-border-color: #565656;--bs-btn-focus-shadow-rgb: 90, 90, 90;--bs-btn-active-color: #fff;--bs-btn-active-bg: dimgray;--bs-btn-active-border-color: #565656;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #434343;--bs-btn-disabled-border-color: #434343}nav.quarto-secondary-nav.color-navbar{background-color:#375a7f;color:#dee2e6}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#dee2e6}@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}/*! dark */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:#222}.code-annotation-gutter{background-color:rgba(67,67,67,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#e6e6e6;border:solid #e6e6e6 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:#222;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:#434343;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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:#222}.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:#222}.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:#222}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#222}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#222}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#222}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#6f6f6f;z-index:998;opacity:.999;margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table{border-top:1px solid #fff;border-bottom:1px solid #fff}.table>thead{border-top-width:0;border-bottom:1px solid #fff}.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}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}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}.blockquote-footer{color:#595959}.form-floating>label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label{color:#595959}.nav-tabs .nav-link,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover,.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-pills .nav-link,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover,.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover{color:#fff}.breadcrumb a{color:#fff}.pagination a:hover{text-decoration:none}.alert{color:#fff;border:none}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert-default{background-color:#434343}.alert-primary{background-color:#375a7f}.alert-secondary{background-color:#434343}.alert-success{background-color:#00bc8c}.alert-info{background-color:#3498db}.alert-warning{background-color:#f39c12}.alert-danger{background-color:#e74c3c}.alert-light{background-color:#6f6f6f}.alert-dark{background-color:#2d2d2d}.tooltip{--bs-tooltip-bg: var(--bs-tertiary-bg);--bs-tooltip-color: var(--bs-emphasis-color)}.dataTables_wrapper{position:relative;clear:both;color:#c3c3c3}*[style*="color: black"]{color:#6d96c0 !important}*[style*="color: darkgreen"]{color:#38c538 !important}.flextable-shadow-host{background-color:#fff !important}.quarto-title-banner{margin-bottom:1em;color:#dee2e6;background:#375a7f}.quarto-title-banner a{color:#dee2e6}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#dee2e6}.quarto-title-banner .code-tools-button{color:#a4afba}.quarto-title-banner .code-tools-button:hover{color:#dee2e6}.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:repeat(2, 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/html_outputs/site_libs/bootstrap/bootstrap.min.css b/html_outputs/site_libs/bootstrap/bootstrap.min.css index dd1c3128..302dcded 100644 --- a/html_outputs/site_libs/bootstrap/bootstrap.min.css +++ b/html_outputs/site_libs/bootstrap/bootstrap.min.css @@ -2,11 +2,11 @@ * Bootstrap v5.3.1 (https://getbootstrap.com/) * Copyright 2011-2023 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root,[data-bs-theme=light]{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-black: #000;--bs-white: #fff;--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: #343a40;--bs-primary: #2780e3;--bs-secondary: #343a40;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #343a40;--bs-default-rgb: 52, 58, 64;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 52, 58, 64;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 52, 58, 64;--bs-primary-text-emphasis: #10335b;--bs-secondary-text-emphasis: #15171a;--bs-success-text-emphasis: #19490a;--bs-info-text-emphasis: #3d224b;--bs-warning-text-emphasis: #662f0a;--bs-danger-text-emphasis: #660017;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4e6f9;--bs-secondary-bg-subtle: #d6d8d9;--bs-success-bg-subtle: #d9f0d1;--bs-info-bg-subtle: #ebddf1;--bs-warning-bg-subtle: #ffe3d1;--bs-danger-bg-subtle: #ffccd7;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9ccf4;--bs-secondary-border-subtle: #aeb0b3;--bs-success-border-subtle: #b2e2a3;--bs-info-border-subtle: #d6bbe4;--bs-warning-border-subtle: #ffc8a3;--bs-danger-border-subtle: #ff99b0;--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: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--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: 17px;--bs-body-font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #343a40;--bs-body-color-rgb: 52, 58, 64;--bs-body-bg: #fff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(52, 58, 64, 0.75);--bs-secondary-color-rgb: 52, 58, 64;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(52, 58, 64, 0.5);--bs-tertiary-color-rgb: 52, 58, 64;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #2761e3;--bs-link-color-rgb: 39, 97, 227;--bs-link-decoration: underline;--bs-link-hover-color: #1f4eb6;--bs-link-hover-color-rgb: 31, 78, 182;--bs-code-color: #7d12ba;--bs-highlight-bg: #ffe3d1;--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(39, 128, 227, 0.25);--bs-form-valid-color: #3fb618;--bs-form-valid-border-color: #3fb618;--bs-form-invalid-color: #ff0039;--bs-form-invalid-border-color: #ff0039}[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: #fff;--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: #7db3ee;--bs-secondary-text-emphasis: #85898c;--bs-success-text-emphasis: #8cd374;--bs-info-text-emphasis: #c298d6;--bs-warning-text-emphasis: #ffac74;--bs-danger-text-emphasis: #ff6688;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #081a2d;--bs-secondary-bg-subtle: #0a0c0d;--bs-success-bg-subtle: #0d2405;--bs-info-bg-subtle: #1f1125;--bs-warning-bg-subtle: #331705;--bs-danger-bg-subtle: #33000b;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #174d88;--bs-secondary-border-subtle: #1f2326;--bs-success-border-subtle: #266d0e;--bs-info-border-subtle: #5c3270;--bs-warning-border-subtle: #99460e;--bs-danger-border-subtle: #990022;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #7db3ee;--bs-link-hover-color: #97c2f1;--bs-link-color-rgb: 125, 179, 238;--bs-link-hover-color-rgb: 151, 194, 241;--bs-code-color: white;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #8cd374;--bs-form-valid-border-color: #8cd374;--bs-form-invalid-color: #ff6688;--bs-form-invalid-border-color: #ff6688}*,*::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:400;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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6)}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:#f8f9fa;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#343a40}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(52,58,64,.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;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(52,58,64,.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: #343a40;--bs-table-bg: #fff;--bs-table-border-color: #dee2e6;--bs-table-accent-bg: transparent;--bs-table-striped-color: #343a40;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #343a40;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #343a40;--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 #b2bac1}.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: #d4e6f9;--bs-table-border-color: #bfcfe0;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--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: #d6d8d9;--bs-table-border-color: #c1c2c3;--bs-table-striped-bg: #cbcdce;--bs-table-striped-color: #000;--bs-table-active-bg: #c1c2c3;--bs-table-active-color: #000;--bs-table-hover-bg: #c6c8c9;--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: #d9f0d1;--bs-table-border-color: #c3d8bc;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--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: #ebddf1;--bs-table-border-color: #d4c7d9;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--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: #ffe3d1;--bs-table-border-color: #e6ccbc;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--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: #ffccd7;--bs-table-border-color: #e6b8c2;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--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: #fff;--bs-table-bg: #343a40;--bs-table-border-color: #484e53;--bs-table-striped-bg: #3e444a;--bs-table-striped-color: #fff;--bs-table-active-bg: #484e53;--bs-table-active-color: #fff;--bs-table-hover-bg: #43494e;--bs-table-hover-color: #fff;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(52,58,64,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#343a40;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:0;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:#343a40;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.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(52,58,64,.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:#343a40;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:#343a40;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}.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}.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}.form-control-color::-webkit-color-swatch{border:0 !important}.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:#343a40;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:0;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:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.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 #343a40}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[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: #fff;width:1em;height:1em;margin-top:.25em;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=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:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.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:#2780e3;border-color:#2780e3}.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='%23fff' 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='%23fff'/%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:#2780e3;border-color:#2780e3;--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='%23fff' 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;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='%2393c0f1'/%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='%23fff'/%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(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.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:#2780e3;border:0;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:#bed9f7}.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)}.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:#2780e3;border:0;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:#bed9f7}.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)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(52,58,64,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(52,58,64,.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}.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:#343a40;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #dee2e6}.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}.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}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.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)}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.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:#3fb618}.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:#3fb618;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='%233fb618' 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:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.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:#3fb618}.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='%233fb618' 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:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.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:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.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:#ff0039}.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:#ff0039}.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:#ff0039;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='%23ff0039'%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='%23ff0039' 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:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.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:#ff0039}.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='%23ff0039'%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='%23ff0039' 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:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.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:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.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: #343a40;--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);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: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c3136;--bs-btn-hover-border-color: #2a2e33;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a2e33;--bs-btn-active-border-color: #272c30;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #216dc1;--bs-btn-hover-border-color: #1f66b6;--bs-btn-focus-shadow-rgb: 71, 147, 231;--bs-btn-active-color: #fff;--bs-btn-active-bg: #1f66b6;--bs-btn-active-border-color: #1d60aa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2780e3;--bs-btn-disabled-border-color: #2780e3}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c3136;--bs-btn-hover-border-color: #2a2e33;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a2e33;--bs-btn-active-border-color: #272c30;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #369b14;--bs-btn-hover-border-color: #329213;--bs-btn-focus-shadow-rgb: 92, 193, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #329213;--bs-btn-active-border-color: #2f8912;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3fb618;--bs-btn-disabled-border-color: #3fb618}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #82479f;--bs-btn-hover-border-color: #7a4396;--bs-btn-focus-shadow-rgb: 168, 110, 197;--bs-btn-active-color: #fff;--bs-btn-active-bg: #7a4396;--bs-btn-active-border-color: #733f8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #9954bb;--bs-btn-disabled-border-color: #9954bb}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d96314;--bs-btn-hover-border-color: #cc5e13;--bs-btn-focus-shadow-rgb: 255, 138, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc5e13;--bs-btn-active-border-color: #bf5812;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff7518;--bs-btn-disabled-border-color: #ff7518}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d90030;--bs-btn-hover-border-color: #cc002e;--bs-btn-focus-shadow-rgb: 255, 38, 87;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc002e;--bs-btn-active-border-color: #bf002b;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff0039;--bs-btn-disabled-border-color: #ff0039}.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: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #52585d;--bs-btn-hover-border-color: #484e53;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-outline-default{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2780e3;--bs-btn-hover-border-color: #2780e3;--bs-btn-focus-shadow-rgb: 39, 128, 227;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2780e3;--bs-btn-active-border-color: #2780e3;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2780e3;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2780e3;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3fb618;--bs-btn-hover-border-color: #3fb618;--bs-btn-focus-shadow-rgb: 63, 182, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3fb618;--bs-btn-active-border-color: #3fb618;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3fb618;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3fb618;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #9954bb;--bs-btn-hover-border-color: #9954bb;--bs-btn-focus-shadow-rgb: 153, 84, 187;--bs-btn-active-color: #fff;--bs-btn-active-bg: #9954bb;--bs-btn-active-border-color: #9954bb;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #9954bb;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #9954bb;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff7518;--bs-btn-hover-border-color: #ff7518;--bs-btn-focus-shadow-rgb: 255, 117, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff7518;--bs-btn-active-border-color: #ff7518;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff7518;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff7518;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff0039;--bs-btn-hover-border-color: #ff0039;--bs-btn-focus-shadow-rgb: 255, 0, 57;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff0039;--bs-btn-active-border-color: #ff0039;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff0039;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff0039;--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: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #2761e3;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #1f4eb6;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #1f4eb6;--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: 71, 121, 231;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: #343a40;--bs-dropdown-bg: #fff;--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: #343a40;--bs-dropdown-link-hover-color: #343a40;--bs-dropdown-link-hover-bg: #f8f9fa;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: rgba(52, 58, 64, 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)}.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}.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: #fff;--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: #fff;--bs-dropdown-link-active-bg: #2780e3;--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>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(1px*-1)}.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)}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #2761e3;--bs-nav-link-hover-color: #1f4eb6;--bs-nav-link-disabled-color: rgba(52, 58, 64, 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(39,128,227,.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: #fff;--bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff;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)}.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))}.nav-pills{--bs-nav-pills-border-radius: 0.25rem;--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #2780e3}.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: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--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='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(84, 85, 85, 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);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: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--bs-navbar-toggler-border-color: rgba(84, 85, 85, 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='%23545555' 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='%23545555' 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: #fff;--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)}.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}.card>.list-group:last-child{border-bottom-width:0}.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-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-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)}.card-img,.card-img-top,.card-img-bottom{width:100%}.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}}.accordion{--bs-accordion-color: #343a40;--bs-accordion-bg: #fff;--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: #343a40;--bs-accordion-btn-bg: #fff;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23343a40'%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='%2310335b'%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: #93c0f1;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #10335b;--bs-accordion-active-bg: #d4e6f9}.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;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:not(:first-of-type){border-top:0}.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}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom: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='%237db3ee'%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='%237db3ee'%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(52, 58, 64, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(52, 58, 64, 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)}.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: #2761e3;--bs-pagination-bg: #fff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.25rem;--bs-pagination-hover-color: #1f4eb6;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #1f4eb6;--bs-pagination-focus-bg: #e9ecef;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #2780e3;--bs-pagination-active-border-color: #2780e3;--bs-pagination-disabled-color: rgba(52, 58, 64, 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)}.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: #fff;--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}.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: 0 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)}.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:.5rem}}.progress,.progress-stacked{--bs-progress-height: 0.5rem;--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: #fff;--bs-progress-bar-bg: #2780e3;--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)}.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: #343a40;--bs-list-group-bg: #fff;--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(52, 58, 64, 0.75);--bs-list-group-action-hover-color: #000;--bs-list-group-action-hover-bg: #f8f9fa;--bs-list-group-action-active-color: #343a40;--bs-list-group-action-active-bg: #e9ecef;--bs-list-group-disabled-color: rgba(52, 58, 64, 0.75);--bs-list-group-disabled-bg: #fff;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #2780e3;--bs-list-group-active-border-color: #2780e3;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.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.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.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.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.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.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.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.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>.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(39, 128, 227, 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;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(52, 58, 64, 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)}.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)}.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: #fff;--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);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)}.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)}.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}.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}.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}.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}.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}.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}.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: #fff;--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:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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)}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: 276px;--bs-popover-font-size:0.875rem;--bs-popover-bg: #fff;--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: #343a40;--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:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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)}.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)}.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='%23fff'%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='%23fff'%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: #343a40;--bs-offcanvas-bg: #fff;--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:#fff !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:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !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(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, 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(31, 102, 182, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(31, 102, 182, 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(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, 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(50, 146, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(50, 146, 19, 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(122, 67, 150, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(122, 67, 150, 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(204, 94, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 94, 19, 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(204, 0, 46, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 0, 46, 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(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, 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.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: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:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.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: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;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: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #343a40}.bg-default{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #343a40}.bg-secondary{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #343a40}.bg-dark{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;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: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #343a40}.bg-default{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #343a40}.bg-secondary{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #343a40}.bg-dark{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}: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}.accordion .accordion-header{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;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)}.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)}}@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}}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)}}.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)}:root{--bslib-page-sidebar-title-bg: #f8f9fa;--bslib-page-sidebar-title-color: #000}.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}}: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, #fff);--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:400;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:400;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%)}.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:#343a40}.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:#545555}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#1f4eb6}.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:#595959}.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:#595959;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(33,81,191,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#8c8c8c}.breadcrumb-item{line-height:1.2rem}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(33,81,191,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#2151bf}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@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:#fff;border-bottom:1px solid #dee2e6}.quarto-banner nav.quarto-secondary-nav{background-color:#f8f9fa;color:#545555;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:#1f4eb6}.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:#545555;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:#595959;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:#545555;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#545555;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;color:#343a40;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(39,128,227,.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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#2780e3}.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:#2780e3}.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:#4b95e8}.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:#343a40}.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:#e5effc}.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:#343a40}.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:#343a40}.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:#545555}.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:#595959}.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:#343a40;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(39,128,227,.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(52,58,64,.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:#5397e9 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-secondary{background-color:#343a40 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-success{background-color:#3aa716 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-info{background-color:rgba(153,84,187,.7019607843) !important}.quarto-dashboard .card.valuebox.bslib-card.bg-warning{background-color:#fa6400 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-danger{background-color:rgba(255,0,57,.7019607843) !important}.quarto-dashboard .card.valuebox.bslib-card.bg-light{background-color:#f8f9fa !important}.quarto-dashboard .card.valuebox.bslib-card.bg-dark{background-color:#343a40 !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 #dae0e5;border-bottom:solid 1px #dae0e5}.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:#e0e5e9}.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:#545555}.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:#343a40}.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:#343a40}.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 #bcbfc0;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:#343a40}.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}.tableFloatingHeaderOriginal{background-color:#fff;position:sticky !important;top:0 !important}.dashboard-data-table{margin-top:-1px}.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:#343a40;text-decoration:none}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#343a40;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:#343a40}.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}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}/*! + */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root,[data-bs-theme=light]{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-black: #000;--bs-white: #fff;--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: #343a40;--bs-primary: #2780e3;--bs-secondary: #343a40;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #343a40;--bs-default-rgb: 52, 58, 64;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 52, 58, 64;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 52, 58, 64;--bs-primary-text-emphasis: #10335b;--bs-secondary-text-emphasis: #15171a;--bs-success-text-emphasis: #19490a;--bs-info-text-emphasis: #3d224b;--bs-warning-text-emphasis: #662f0a;--bs-danger-text-emphasis: #660017;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4e6f9;--bs-secondary-bg-subtle: #d6d8d9;--bs-success-bg-subtle: #d9f0d1;--bs-info-bg-subtle: #ebddf1;--bs-warning-bg-subtle: #ffe3d1;--bs-danger-bg-subtle: #ffccd7;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9ccf4;--bs-secondary-border-subtle: #aeb0b3;--bs-success-border-subtle: #b2e2a3;--bs-info-border-subtle: #d6bbe4;--bs-warning-border-subtle: #ffc8a3;--bs-danger-border-subtle: #ff99b0;--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: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--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: 17px;--bs-body-font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #343a40;--bs-body-color-rgb: 52, 58, 64;--bs-body-bg: #fff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(52, 58, 64, 0.75);--bs-secondary-color-rgb: 52, 58, 64;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(52, 58, 64, 0.5);--bs-tertiary-color-rgb: 52, 58, 64;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #2761e3;--bs-link-color-rgb: 39, 97, 227;--bs-link-decoration: underline;--bs-link-hover-color: #1f4eb6;--bs-link-hover-color-rgb: 31, 78, 182;--bs-code-color: #7d12ba;--bs-highlight-bg: #ffe3d1;--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(39, 128, 227, 0.25);--bs-form-valid-color: #3fb618;--bs-form-valid-border-color: #3fb618;--bs-form-invalid-color: #ff0039;--bs-form-invalid-border-color: #ff0039}[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: #fff;--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: #7db3ee;--bs-secondary-text-emphasis: #85898c;--bs-success-text-emphasis: #8cd374;--bs-info-text-emphasis: #c298d6;--bs-warning-text-emphasis: #ffac74;--bs-danger-text-emphasis: #ff6688;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #081a2d;--bs-secondary-bg-subtle: #0a0c0d;--bs-success-bg-subtle: #0d2405;--bs-info-bg-subtle: #1f1125;--bs-warning-bg-subtle: #331705;--bs-danger-bg-subtle: #33000b;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #174d88;--bs-secondary-border-subtle: #1f2326;--bs-success-border-subtle: #266d0e;--bs-info-border-subtle: #5c3270;--bs-warning-border-subtle: #99460e;--bs-danger-border-subtle: #990022;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #7db3ee;--bs-link-hover-color: #97c2f1;--bs-link-color-rgb: 125, 179, 238;--bs-link-hover-color-rgb: 151, 194, 241;--bs-code-color: white;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #8cd374;--bs-form-valid-border-color: #8cd374;--bs-form-invalid-color: #ff6688;--bs-form-invalid-border-color: #ff6688}*,*::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:400;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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6)}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:#f8f9fa;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#343a40}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(52,58,64,.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;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(52,58,64,.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: #343a40;--bs-table-bg: #fff;--bs-table-border-color: #dee2e6;--bs-table-accent-bg: transparent;--bs-table-striped-color: #343a40;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #343a40;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #343a40;--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 #b2bac1}.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: #d4e6f9;--bs-table-border-color: #bfcfe0;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--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: #d6d8d9;--bs-table-border-color: #c1c2c3;--bs-table-striped-bg: #cbcdce;--bs-table-striped-color: #000;--bs-table-active-bg: #c1c2c3;--bs-table-active-color: #000;--bs-table-hover-bg: #c6c8c9;--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: #d9f0d1;--bs-table-border-color: #c3d8bc;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--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: #ebddf1;--bs-table-border-color: #d4c7d9;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--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: #ffe3d1;--bs-table-border-color: #e6ccbc;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--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: #ffccd7;--bs-table-border-color: #e6b8c2;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--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: #fff;--bs-table-bg: #343a40;--bs-table-border-color: #484e53;--bs-table-striped-bg: #3e444a;--bs-table-striped-color: #fff;--bs-table-active-bg: #484e53;--bs-table-active-color: #fff;--bs-table-hover-bg: #43494e;--bs-table-hover-color: #fff;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(52,58,64,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#343a40;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:0;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:#343a40;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.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(52,58,64,.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:#343a40;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:#343a40;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}.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}.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}.form-control-color::-webkit-color-swatch{border:0 !important}.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:#343a40;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:0;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:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.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 #343a40}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[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: #fff;width:1em;height:1em;margin-top:.25em;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=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:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.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:#2780e3;border-color:#2780e3}.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='%23fff' 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='%23fff'/%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:#2780e3;border-color:#2780e3;--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='%23fff' 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;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='%2393c0f1'/%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='%23fff'/%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(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.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:#2780e3;border:0;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:#bed9f7}.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)}.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:#2780e3;border:0;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:#bed9f7}.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)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(52,58,64,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(52,58,64,.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}.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:#343a40;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #dee2e6}.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}.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}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.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)}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.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:#3fb618}.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:#3fb618;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='%233fb618' 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:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.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:#3fb618}.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='%233fb618' 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:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.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:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.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:#ff0039}.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:#ff0039}.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:#ff0039;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='%23ff0039'%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='%23ff0039' 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:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.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:#ff0039}.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='%23ff0039'%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='%23ff0039' 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:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.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:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.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: #343a40;--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);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: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c3136;--bs-btn-hover-border-color: #2a2e33;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a2e33;--bs-btn-active-border-color: #272c30;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #216dc1;--bs-btn-hover-border-color: #1f66b6;--bs-btn-focus-shadow-rgb: 71, 147, 231;--bs-btn-active-color: #fff;--bs-btn-active-bg: #1f66b6;--bs-btn-active-border-color: #1d60aa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2780e3;--bs-btn-disabled-border-color: #2780e3}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2c3136;--bs-btn-hover-border-color: #2a2e33;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2a2e33;--bs-btn-active-border-color: #272c30;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #369b14;--bs-btn-hover-border-color: #329213;--bs-btn-focus-shadow-rgb: 92, 193, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #329213;--bs-btn-active-border-color: #2f8912;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3fb618;--bs-btn-disabled-border-color: #3fb618}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #82479f;--bs-btn-hover-border-color: #7a4396;--bs-btn-focus-shadow-rgb: 168, 110, 197;--bs-btn-active-color: #fff;--bs-btn-active-bg: #7a4396;--bs-btn-active-border-color: #733f8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #9954bb;--bs-btn-disabled-border-color: #9954bb}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d96314;--bs-btn-hover-border-color: #cc5e13;--bs-btn-focus-shadow-rgb: 255, 138, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc5e13;--bs-btn-active-border-color: #bf5812;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff7518;--bs-btn-disabled-border-color: #ff7518}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d90030;--bs-btn-hover-border-color: #cc002e;--bs-btn-focus-shadow-rgb: 255, 38, 87;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc002e;--bs-btn-active-border-color: #bf002b;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff0039;--bs-btn-disabled-border-color: #ff0039}.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: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #52585d;--bs-btn-hover-border-color: #484e53;--bs-btn-focus-shadow-rgb: 82, 88, 93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-outline-default{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2780e3;--bs-btn-hover-border-color: #2780e3;--bs-btn-focus-shadow-rgb: 39, 128, 227;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2780e3;--bs-btn-active-border-color: #2780e3;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2780e3;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2780e3;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3fb618;--bs-btn-hover-border-color: #3fb618;--bs-btn-focus-shadow-rgb: 63, 182, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3fb618;--bs-btn-active-border-color: #3fb618;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3fb618;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3fb618;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #9954bb;--bs-btn-hover-border-color: #9954bb;--bs-btn-focus-shadow-rgb: 153, 84, 187;--bs-btn-active-color: #fff;--bs-btn-active-bg: #9954bb;--bs-btn-active-border-color: #9954bb;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #9954bb;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #9954bb;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff7518;--bs-btn-hover-border-color: #ff7518;--bs-btn-focus-shadow-rgb: 255, 117, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff7518;--bs-btn-active-border-color: #ff7518;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff7518;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff7518;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff0039;--bs-btn-hover-border-color: #ff0039;--bs-btn-focus-shadow-rgb: 255, 0, 57;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff0039;--bs-btn-active-border-color: #ff0039;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff0039;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff0039;--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: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52, 58, 64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #2761e3;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #1f4eb6;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #1f4eb6;--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: 71, 121, 231;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: #343a40;--bs-dropdown-bg: #fff;--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: #343a40;--bs-dropdown-link-hover-color: #343a40;--bs-dropdown-link-hover-bg: #f8f9fa;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: rgba(52, 58, 64, 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)}.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}.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: #fff;--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: #fff;--bs-dropdown-link-active-bg: #2780e3;--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>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(1px*-1)}.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)}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #2761e3;--bs-nav-link-hover-color: #1f4eb6;--bs-nav-link-disabled-color: rgba(52, 58, 64, 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(39,128,227,.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: #fff;--bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff;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)}.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))}.nav-pills{--bs-nav-pills-border-radius: 0.25rem;--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #2780e3}.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: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--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='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(84, 85, 85, 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);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: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--bs-navbar-toggler-border-color: rgba(84, 85, 85, 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='%23545555' 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='%23545555' 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: #fff;--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)}.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}.card>.list-group:last-child{border-bottom-width:0}.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-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-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)}.card-img,.card-img-top,.card-img-bottom{width:100%}.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}}.accordion{--bs-accordion-color: #343a40;--bs-accordion-bg: #fff;--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: #343a40;--bs-accordion-btn-bg: #fff;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23343a40'%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='%2310335b'%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: #93c0f1;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #10335b;--bs-accordion-active-bg: #d4e6f9}.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;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:not(:first-of-type){border-top:0}.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}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom: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='%237db3ee'%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='%237db3ee'%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(52, 58, 64, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(52, 58, 64, 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)}.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: #2761e3;--bs-pagination-bg: #fff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.25rem;--bs-pagination-hover-color: #1f4eb6;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #1f4eb6;--bs-pagination-focus-bg: #e9ecef;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #2780e3;--bs-pagination-active-border-color: #2780e3;--bs-pagination-disabled-color: rgba(52, 58, 64, 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)}.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: #fff;--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}.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: 0 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)}.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:.5rem}}.progress,.progress-stacked{--bs-progress-height: 0.5rem;--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: #fff;--bs-progress-bar-bg: #2780e3;--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)}.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: #343a40;--bs-list-group-bg: #fff;--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(52, 58, 64, 0.75);--bs-list-group-action-hover-color: #000;--bs-list-group-action-hover-bg: #f8f9fa;--bs-list-group-action-active-color: #343a40;--bs-list-group-action-active-bg: #e9ecef;--bs-list-group-disabled-color: rgba(52, 58, 64, 0.75);--bs-list-group-disabled-bg: #fff;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #2780e3;--bs-list-group-active-border-color: #2780e3;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.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.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.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.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.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.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.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.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>.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(39, 128, 227, 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;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(52, 58, 64, 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)}.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)}.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: #fff;--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);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)}.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)}.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}.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}.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}.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}.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}.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}.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: #fff;--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:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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)}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: 276px;--bs-popover-font-size:0.875rem;--bs-popover-bg: #fff;--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: #343a40;--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:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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;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)}.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)}.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='%23fff'%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='%23fff'%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: #343a40;--bs-offcanvas-bg: #fff;--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:#fff !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:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !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(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, 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(31, 102, 182, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(31, 102, 182, 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(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, 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(50, 146, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(50, 146, 19, 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(122, 67, 150, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(122, 67, 150, 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(204, 94, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 94, 19, 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(204, 0, 46, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 0, 46, 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(42, 46, 51, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(42, 46, 51, 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.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: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:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.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: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;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: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #343a40}.bg-default{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #343a40}.bg-secondary{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #343a40}.bg-dark{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;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: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;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: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #343a40}.bg-default{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #343a40}.bg-secondary{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #343a40}.bg-dark{--bslib-color-bg: #343a40;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--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: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--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:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}: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}.accordion .accordion-header{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;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)}.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)}}@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}}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)}}.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)}:root{--bslib-page-sidebar-title-bg: #f8f9fa;--bslib-page-sidebar-title-color: #000}.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}}: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, #fff);--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:400;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:400;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%)}.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:#343a40}.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-collapse .quarto-navbar-tools{margin-left:.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:#545555}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#1f4eb6}.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:#595959}.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:#595959;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(33,81,191,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#8c8c8c}.breadcrumb-item{line-height:1.2rem}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(33,81,191,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#2151bf}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@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:#fff;border-bottom:1px solid #dee2e6}.quarto-banner nav.quarto-secondary-nav{background-color:#f8f9fa;color:#545555;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(min-width: 992px){#quarto-sidebar-glass{display:none}}@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:#1f4eb6}.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}@media(min-width: 768px){.nav-footer-left{flex:1 1 0px;text-align:left}}@media(max-width: 575.98px){.nav-footer-left{margin-bottom:1em;flex:100%}}@media(min-width: 768px){.nav-footer-right{flex:1 1 0px;text-align:right}}@media(max-width: 575.98px){.nav-footer-right{margin-bottom:1em;flex:100%}}.nav-footer-center{text-align:center;min-height:3em}@media(min-width: 768px){.nav-footer-center{flex:1 1 0px}}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-bottom:1em;flex:100%}}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em;order:10}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#545555;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:#595959;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)}#quarto-announcement{padding:.5em;display:flex;justify-content:space-between;margin-bottom:0;font-size:.9em}#quarto-announcement .quarto-announcement-content{margin-right:auto}#quarto-announcement .quarto-announcement-content p{margin-bottom:0}#quarto-announcement .quarto-announcement-icon{margin-right:.5em;font-size:1.2em;margin-top:-0.15em}#quarto-announcement .quarto-announcement-action{cursor:pointer}.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{order:900}}@media(min-width: 992px){.navbar .quarto-navbar-tools.tools-end{margin-left:auto !important}}@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:#545555;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#545555;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;color:#343a40;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(39,128,227,.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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#343a40;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:#2780e3}.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:#2780e3}.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:#4b95e8}.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:#343a40}.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:#e5effc}.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:#343a40}.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:#343a40}.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:#545555}.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:#595959}.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:#343a40;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(39,128,227,.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(52,58,64,.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:#5397e9 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-secondary{background-color:#343a40 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-success{background-color:#3aa716 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-info{background-color:rgba(153,84,187,.7019607843) !important}.quarto-dashboard .card.valuebox.bslib-card.bg-warning{background-color:#fa6400 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-danger{background-color:rgba(255,0,57,.7019607843) !important}.quarto-dashboard .card.valuebox.bslib-card.bg-light{background-color:#f8f9fa !important}.quarto-dashboard .card.valuebox.bslib-card.bg-dark{background-color:#343a40 !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 #dae0e5;border-bottom:solid 1px #dae0e5}.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:#e0e5e9}.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:#545555}.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:#343a40}.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:#343a40}.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 #bcbfc0;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:4}@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:#343a40}.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}.tableFloatingHeaderOriginal{background-color:#fff;position:sticky !important;top:0 !important}.dashboard-data-table{margin-top:-1px}div.value-box-area span.observablehq--number{font-size:calc(clamp(.1em,15cqw,5em)*1.25);line-height:1.2;color:inherit;font-family:var(--bs-body-font-family)}.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:#343a40;text-decoration:none}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#626d78;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:#2761e3}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:#343a40;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:#343a40}.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}dd code:not(.sourceCode),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}.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 * * 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: #fff;--quarto-body-color: #343a40;--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: #fff;--mermaid-edge-color: #343a40;--mermaid-node-fg-color: #343a40;--mermaid-fg-color: #343a40;--mermaid-fg-color--lighter: #4b545c;--mermaid-fg-color--lightest: #626d78;--mermaid-font-family: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #fff;--mermaid-label-fg-color: #2780e3;--mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--mermaid-node-fg-color: #343a40}@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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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:#6d7a86}.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:#6d7a86}.quarto-layout-cell[data-ref-parent] caption{color:#6d7a86}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#6d7a86;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(233,236,239,.65);border:1px solid rgba(233,236,239,.65);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:#6d7a86}.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:#f8f9fa;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:#2761e3}.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:#2761e3}.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 #2761e3;color:#2761e3 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2761e3 !important}kbd,.kbd{color:#343a40;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:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}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:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}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:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}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:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}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:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}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:#f8f9fa;color:#545555}.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:#343a40}.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: #cacccd;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #cacccd;--bs-btn-hover-bg: #52585d;--bs-btn-hover-border-color: #484e53;--bs-btn-focus-shadow-rgb: 75, 80, 85;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}nav.quarto-secondary-nav.color-navbar{background-color:#f8f9fa;color:#545555}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#545555}@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(233,236,239,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#4b545c;border:solid #4b545c 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:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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 #ebedee;border-bottom:1px solid #ebedee}.table>thead{border-top-width:0;border-bottom:1px solid #b2bac1}.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:#545555;background:#f8f9fa}.quarto-title-banner a{color:#545555}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#545555}.quarto-title-banner .code-tools-button{color:#878888}.quarto-title-banner .code-tools-button:hover{color:#545555}.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}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#343a40}.progress .progress-bar{font-size:8px;line-height:8px} +*/.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: #fff;--quarto-body-color: #343a40;--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: #fff;--mermaid-edge-color: #343a40;--mermaid-node-fg-color: #343a40;--mermaid-fg-color: #343a40;--mermaid-fg-color--lighter: #4b545c;--mermaid-fg-color--lightest: #626d78;--mermaid-font-family: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #fff;--mermaid-label-fg-color: #2780e3;--mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--mermaid-node-fg-color: #343a40}@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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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:#6d7a86}.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:#6d7a86}.quarto-layout-cell[data-ref-parent] caption{color:#6d7a86}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#6d7a86;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(233,236,239,.65);border:1px solid rgba(233,236,239,.65);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:#6d7a86}.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:#f8f9fa;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:#2761e3}.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:#2761e3}.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 #2761e3;color:#2761e3 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2761e3 !important}kbd,.kbd{color:#343a40;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:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}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:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}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:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}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:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}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:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}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:#f8f9fa;color:#545555}.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:#343a40}.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: #cacccd;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #cacccd;--bs-btn-hover-bg: #52585d;--bs-btn-hover-border-color: #484e53;--bs-btn-focus-shadow-rgb: 75, 80, 85;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}nav.quarto-secondary-nav.color-navbar{background-color:#f8f9fa;color:#545555}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#545555}@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(233,236,239,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#4b545c;border:solid #4b545c 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:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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 #ebedee;border-bottom:1px solid #ebedee}.table>thead{border-top-width:0;border-bottom:1px solid #b2bac1}.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}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}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:#545555;background:#f8f9fa}.quarto-title-banner a{color:#545555}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#545555}.quarto-title-banner .code-tools-button{color:#878888}.quarto-title-banner .code-tools-button:hover{color:#545555}.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:repeat(2, 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}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#343a40}.progress .progress-bar{font-size:8px;line-height:8px} diff --git a/html_outputs/site_libs/datatables-binding-0.33/datatables.js b/html_outputs/site_libs/datatables-binding-0.33/datatables.js new file mode 100644 index 00000000..765b53cb --- /dev/null +++ b/html_outputs/site_libs/datatables-binding-0.33/datatables.js @@ -0,0 +1,1539 @@ +(function() { + +// some helper functions: using a global object DTWidget so that it can be used +// in JS() code, e.g. datatable(options = list(foo = JS('code'))); unlike R's +// dynamic scoping, when 'code' is eval'ed, JavaScript does not know objects +// from the "parent frame", e.g. JS('DTWidget') will not work unless it was made +// a global object +var DTWidget = {}; + +// 123456666.7890 -> 123,456,666.7890 +var markInterval = function(d, digits, interval, mark, decMark, precision) { + x = precision ? d.toPrecision(digits) : d.toFixed(digits); + if (!/^-?[\d.]+$/.test(x)) return x; + var xv = x.split('.'); + if (xv.length > 2) return x; // should have at most one decimal point + xv[0] = xv[0].replace(new RegExp('\\B(?=(\\d{' + interval + '})+(?!\\d))', 'g'), mark); + return xv.join(decMark); +}; + +DTWidget.formatCurrency = function(data, currency, digits, interval, mark, decMark, before, zeroPrint) { + var d = parseFloat(data); + if (isNaN(d)) return ''; + if (zeroPrint !== null && d === 0.0) return zeroPrint; + var res = markInterval(d, digits, interval, mark, decMark); + res = before ? (/^-/.test(res) ? '-' + currency + res.replace(/^-/, '') : currency + res) : + res + currency; + return res; +}; + +DTWidget.formatString = function(data, prefix, suffix) { + var d = data; + if (d === null) return ''; + return prefix + d + suffix; +}; + +DTWidget.formatPercentage = function(data, digits, interval, mark, decMark, zeroPrint) { + var d = parseFloat(data); + if (isNaN(d)) return ''; + if (zeroPrint !== null && d === 0.0) return zeroPrint; + return markInterval(d * 100, digits, interval, mark, decMark) + '%'; +}; + +DTWidget.formatRound = function(data, digits, interval, mark, decMark, zeroPrint) { + var d = parseFloat(data); + if (isNaN(d)) return ''; + if (zeroPrint !== null && d === 0.0) return zeroPrint; + return markInterval(d, digits, interval, mark, decMark); +}; + +DTWidget.formatSignif = function(data, digits, interval, mark, decMark, zeroPrint) { + var d = parseFloat(data); + if (isNaN(d)) return ''; + if (zeroPrint !== null && d === 0.0) return zeroPrint; + return markInterval(d, digits, interval, mark, decMark, true); +}; + +DTWidget.formatDate = function(data, method, params) { + var d = data; + if (d === null) return ''; + // (new Date('2015-10-28')).toDateString() may return 2015-10-27 because the + // actual time created could be like 'Tue Oct 27 2015 19:00:00 GMT-0500 (CDT)', + // i.e. the date-only string is treated as UTC time instead of local time + if ((method === 'toDateString' || method === 'toLocaleDateString') && /^\d{4,}\D\d{2}\D\d{2}$/.test(d)) { + d = d.split(/\D/); + d = new Date(d[0], d[1] - 1, d[2]); + } else { + d = new Date(d); + } + return d[method].apply(d, params); +}; + +window.DTWidget = DTWidget; + +// A helper function to update the properties of existing filters +var setFilterProps = function(td, props) { + // Update enabled/disabled state + var $input = $(td).find('input').first(); + var searchable = $input.data('searchable'); + $input.prop('disabled', !searchable || props.disabled); + + // Based on the filter type, set its new values + var type = td.getAttribute('data-type'); + if (['factor', 'logical'].includes(type)) { + // Reformat the new dropdown options for use with selectize + var new_vals = props.params.options.map(function(item) { + return { text: item, value: item }; + }); + + // Find the selectize object + var dropdown = $(td).find('.selectized').eq(0)[0].selectize; + + // Note the current values + var old_vals = dropdown.getValue(); + + // Remove the existing values + dropdown.clearOptions(); + + // Add the new options + dropdown.addOption(new_vals); + + // Preserve the existing values + dropdown.setValue(old_vals); + + } else if (['number', 'integer', 'date', 'time'].includes(type)) { + // Apply internal scaling to new limits. Updating scale not yet implemented. + var slider = $(td).find('.noUi-target').eq(0); + var scale = Math.pow(10, Math.max(0, +slider.data('scale') || 0)); + var new_vals = [props.params.min * scale, props.params.max * scale]; + + // Note what the new limits will be just for this filter + var new_lims = new_vals.slice(); + + // Determine the current values and limits + var old_vals = slider.val().map(Number); + var old_lims = slider.noUiSlider('options').range; + old_lims = [old_lims.min, old_lims.max]; + + // Preserve the current values if filters have been applied; otherwise, apply no filtering + if (old_vals[0] != old_lims[0]) { + new_vals[0] = Math.max(old_vals[0], new_vals[0]); + } + + if (old_vals[1] != old_lims[1]) { + new_vals[1] = Math.min(old_vals[1], new_vals[1]); + } + + // Update the endpoints of the slider + slider.noUiSlider({ + start: new_vals, + range: {'min': new_lims[0], 'max': new_lims[1]} + }, true); + } +}; + +var transposeArray2D = function(a) { + return a.length === 0 ? a : HTMLWidgets.transposeArray2D(a); +}; + +var crosstalkPluginsInstalled = false; + +function maybeInstallCrosstalkPlugins() { + if (crosstalkPluginsInstalled) + return; + crosstalkPluginsInstalled = true; + + $.fn.dataTable.ext.afnFiltering.push( + function(oSettings, aData, iDataIndex) { + var ctfilter = oSettings.nTable.ctfilter; + if (ctfilter && !ctfilter[iDataIndex]) + return false; + + var ctselect = oSettings.nTable.ctselect; + if (ctselect && !ctselect[iDataIndex]) + return false; + + return true; + } + ); +} + +HTMLWidgets.widget({ + name: "datatables", + type: "output", + renderOnNullValue: true, + initialize: function(el, width, height) { + // in order that the type=number inputs return a number + $.valHooks.number = { + get: function(el) { + var value = parseFloat(el.value); + return isNaN(value) ? "" : value; + } + }; + $(el).html(' '); + return { + data: null, + ctfilterHandle: new crosstalk.FilterHandle(), + ctfilterSubscription: null, + ctselectHandle: new crosstalk.SelectionHandle(), + ctselectSubscription: null + }; + }, + renderValue: function(el, data, instance) { + if (el.offsetWidth === 0 || el.offsetHeight === 0) { + instance.data = data; + return; + } + instance.data = null; + var $el = $(el); + $el.empty(); + + if (data === null) { + $el.append(' '); + // clear previous Shiny inputs (if any) + for (var i in instance.clearInputs) instance.clearInputs[i](); + instance.clearInputs = {}; + return; + } + + var crosstalkOptions = data.crosstalkOptions; + if (!crosstalkOptions) crosstalkOptions = { + 'key': null, 'group': null + }; + if (crosstalkOptions.group) { + maybeInstallCrosstalkPlugins(); + instance.ctfilterHandle.setGroup(crosstalkOptions.group); + instance.ctselectHandle.setGroup(crosstalkOptions.group); + } + + // if we are in the viewer then we always want to fillContainer and + // and autoHideNavigation (unless the user has explicitly set these) + if (window.HTMLWidgets.viewerMode) { + if (!data.hasOwnProperty("fillContainer")) + data.fillContainer = true; + if (!data.hasOwnProperty("autoHideNavigation")) + data.autoHideNavigation = true; + } + + // propagate fillContainer to instance (so we have it in resize) + instance.fillContainer = data.fillContainer; + + var cells = data.data; + + if (cells instanceof Array) cells = transposeArray2D(cells); + + $el.append(data.container); + var $table = $el.find('table'); + if (data.class) $table.addClass(data.class); + if (data.caption) $table.prepend(data.caption); + + if (!data.selection) data.selection = { + mode: 'none', selected: null, target: 'row', selectable: null + }; + if (HTMLWidgets.shinyMode && data.selection.mode !== 'none' && + data.selection.target === 'row+column') { + if ($table.children('tfoot').length === 0) { + $table.append($('')); + $table.find('thead tr').clone().appendTo($table.find('tfoot')); + } + } + + // column filters + var filterRow; + switch (data.filter) { + case 'top': + $table.children('thead').append(data.filterHTML); + filterRow = $table.find('thead tr:last td'); + break; + case 'bottom': + if ($table.children('tfoot').length === 0) { + $table.append($('')); + } + $table.children('tfoot').prepend(data.filterHTML); + filterRow = $table.find('tfoot tr:first td'); + break; + } + + var options = { searchDelay: 1000 }; + if (cells !== null) $.extend(options, { + data: cells + }); + + // options for fillContainer + var bootstrapActive = typeof($.fn.popover) != 'undefined'; + if (instance.fillContainer) { + + // force scrollX/scrollY and turn off autoWidth + options.scrollX = true; + options.scrollY = "100px"; // can be any value, we'll adjust below + + // if we aren't paginating then move around the info/filter controls + // to save space at the bottom and rephrase the info callback + if (data.options.paging === false) { + + // we know how to do this cleanly for bootstrap, not so much + // for other themes/layouts + if (bootstrapActive) { + options.dom = "<'row'<'col-sm-4'i><'col-sm-8'f>>" + + "<'row'<'col-sm-12'tr>>"; + } + + options.fnInfoCallback = function(oSettings, iStart, iEnd, + iMax, iTotal, sPre) { + return Number(iTotal).toLocaleString() + " records"; + }; + } + } + + // auto hide navigation if requested + // Note, this only works on client-side processing mode as on server-side, + // cells (data.data) is null; In addition, we require the pageLength option + // being provided explicitly to enable this. Despite we may be able to deduce + // the default value of pageLength, it may complicate things so we'd rather + // put this responsiblity to users and warn them on the R side. + if (data.autoHideNavigation === true && data.options.paging !== false) { + // strip all nav if length >= cells + if ((cells instanceof Array) && data.options.pageLength >= cells.length) + options.dom = bootstrapActive ? "<'row'<'col-sm-12'tr>>" : "t"; + // alternatively lean things out for flexdashboard mobile portrait + else if (bootstrapActive && window.FlexDashboard && window.FlexDashboard.isMobilePhone()) + options.dom = "<'row'<'col-sm-12'f>>" + + "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-12'p>>"; + } + + $.extend(true, options, data.options || {}); + + var searchCols = options.searchCols; + if (searchCols) { + searchCols = searchCols.map(function(x) { + return x === null ? '' : x.search; + }); + // FIXME: this means I don't respect the escapeRegex setting + delete options.searchCols; + } + + // server-side processing? + var server = options.serverSide === true; + + // use the dataSrc function to pre-process JSON data returned from R + var DT_rows_all = [], DT_rows_current = []; + if (server && HTMLWidgets.shinyMode && typeof options.ajax === 'object' && + /^session\/[\da-z]+\/dataobj/.test(options.ajax.url) && !options.ajax.dataSrc) { + options.ajax.dataSrc = function(json) { + DT_rows_all = $.makeArray(json.DT_rows_all); + DT_rows_current = $.makeArray(json.DT_rows_current); + var data = json.data; + if (!colReorderEnabled()) return data; + var table = $table.DataTable(), order = table.colReorder.order(), flag = true, i, j, row; + for (i = 0; i < order.length; ++i) if (order[i] !== i) flag = false; + if (flag) return data; + for (i = 0; i < data.length; ++i) { + row = data[i].slice(); + for (j = 0; j < order.length; ++j) data[i][j] = row[order[j]]; + } + return data; + }; + } + + var thiz = this; + if (instance.fillContainer) $table.on('init.dt', function(e) { + thiz.fillAvailableHeight(el, $(el).innerHeight()); + }); + // If the page contains serveral datatables and one of which enables colReorder, + // the table.colReorder.order() function will exist but throws error when called. + // So it seems like the only way to know if colReorder is enabled or not is to + // check the options. + var colReorderEnabled = function() { return "colReorder" in options; }; + var table = $table.DataTable(options); + $el.data('datatable', table); + + if ('rowGroup' in options) { + // Maintain RowGroup dataSrc when columns are reordered (#1109) + table.on('column-reorder', function(e, settings, details) { + var oldDataSrc = table.rowGroup().dataSrc(); + var newDataSrc = details.mapping[oldDataSrc]; + table.rowGroup().dataSrc(newDataSrc); + }); + } + + // Unregister previous Crosstalk event subscriptions, if they exist + if (instance.ctfilterSubscription) { + instance.ctfilterHandle.off("change", instance.ctfilterSubscription); + instance.ctfilterSubscription = null; + } + if (instance.ctselectSubscription) { + instance.ctselectHandle.off("change", instance.ctselectSubscription); + instance.ctselectSubscription = null; + } + + if (!crosstalkOptions.group) { + $table[0].ctfilter = null; + $table[0].ctselect = null; + } else { + var key = crosstalkOptions.key; + function keysToMatches(keys) { + if (!keys) { + return null; + } else { + var selectedKeys = {}; + for (var i = 0; i < keys.length; i++) { + selectedKeys[keys[i]] = true; + } + var matches = {}; + for (var j = 0; j < key.length; j++) { + if (selectedKeys[key[j]]) + matches[j] = true; + } + return matches; + } + } + + function applyCrosstalkFilter(e) { + $table[0].ctfilter = keysToMatches(e.value); + table.draw(); + } + instance.ctfilterSubscription = instance.ctfilterHandle.on("change", applyCrosstalkFilter); + applyCrosstalkFilter({value: instance.ctfilterHandle.filteredKeys}); + + function applyCrosstalkSelection(e) { + if (e.sender !== instance.ctselectHandle) { + table + .rows('.' + selClass, {search: 'applied'}) + .nodes() + .to$() + .removeClass(selClass); + if (selectedRows) + changeInput('rows_selected', selectedRows(), void 0, true); + } + + if (e.sender !== instance.ctselectHandle && e.value && e.value.length) { + var matches = keysToMatches(e.value); + + // persistent selection with plotly (& leaflet) + var ctOpts = crosstalk.var("plotlyCrosstalkOpts").get() || {}; + if (ctOpts.persistent === true) { + var matches = $.extend(matches, $table[0].ctselect); + } + + $table[0].ctselect = matches; + table.draw(); + } else { + if ($table[0].ctselect) { + $table[0].ctselect = null; + table.draw(); + } + } + } + instance.ctselectSubscription = instance.ctselectHandle.on("change", applyCrosstalkSelection); + // TODO: This next line doesn't seem to work when renderDataTable is used + applyCrosstalkSelection({value: instance.ctselectHandle.value}); + } + + var inArray = function(val, array) { + return $.inArray(val, $.makeArray(array)) > -1; + }; + + // search the i-th column + var searchColumn = function(i, value) { + var regex = false, ci = true; + if (options.search) { + regex = options.search.regex, + ci = options.search.caseInsensitive !== false; + } + // need to transpose the column index when colReorder is enabled + if (table.colReorder) i = table.colReorder.transpose(i); + return table.column(i).search(value, regex, !regex, ci); + }; + + if (data.filter !== 'none') { + if (!data.hasOwnProperty('filterSettings')) data.filterSettings = {}; + + filterRow.each(function(i, td) { + + var $td = $(td), type = $td.data('type'), filter; + var $input = $td.children('div').first().children('input'); + var disabled = $input.prop('disabled'); + var searchable = table.settings()[0].aoColumns[i].bSearchable; + $input.prop('disabled', !searchable || disabled); + $input.data('searchable', searchable); // for updating later + $input.on('input blur', function() { + $input.next('span').toggle(Boolean($input.val())); + }); + // Bootstrap sets pointer-events to none and we won't be able to click + // the clear button + $input.next('span').css('pointer-events', 'auto').hide().click(function() { + $(this).hide().prev('input').val('').trigger('input').focus(); + }); + var searchCol; // search string for this column + if (searchCols && searchCols[i]) { + searchCol = searchCols[i]; + $input.val(searchCol).trigger('input'); + } + var $x = $td.children('div').last(); + + // remove the overflow: hidden attribute of the scrollHead + // (otherwise the scrolling table body obscures the filters) + // The workaround and the discussion from + // https://github.com/rstudio/DT/issues/554#issuecomment-518007347 + // Otherwise the filter selection will not be anchored to the values + // when the columns number is many and scrollX is enabled. + var scrollHead = $(el).find('.dataTables_scrollHead,.dataTables_scrollFoot'); + var cssOverflowHead = scrollHead.css('overflow'); + var scrollBody = $(el).find('.dataTables_scrollBody'); + var cssOverflowBody = scrollBody.css('overflow'); + var scrollTable = $(el).find('.dataTables_scroll'); + var cssOverflowTable = scrollTable.css('overflow'); + if (cssOverflowHead === 'hidden') { + $x.on('show hide', function(e) { + if (e.type === 'show') { + scrollHead.css('overflow', 'visible'); + scrollBody.css('overflow', 'visible'); + scrollTable.css('overflow-x', 'scroll'); + } else { + scrollHead.css('overflow', cssOverflowHead); + scrollBody.css('overflow', cssOverflowBody); + scrollTable.css('overflow-x', cssOverflowTable); + } + }); + $x.css('z-index', 25); + } + + if (inArray(type, ['factor', 'logical'])) { + $input.on({ + click: function() { + $input.parent().hide(); $x.show().trigger('show'); filter[0].selectize.focus(); + }, + input: function() { + var v1 = JSON.stringify(filter[0].selectize.getValue()), v2 = $input.val(); + if (v1 === '[]') v1 = ''; + if (v1 !== v2) filter[0].selectize.setValue(v2 === '' ? [] : JSON.parse(v2)); + } + }); + var $input2 = $x.children('select'); + filter = $input2.selectize($.extend({ + options: $input2.data('options').map(function(v, i) { + return ({text: v, value: v}); + }), + plugins: ['remove_button'], + hideSelected: true, + onChange: function(value) { + if (value === null) value = []; // compatibility with jQuery 3.0 + $input.val(value.length ? JSON.stringify(value) : ''); + if (value.length) $input.trigger('input'); + $input.attr('title', $input.val()); + if (server) { + searchColumn(i, value.length ? JSON.stringify(value) : '').draw(); + return; + } + // turn off filter if nothing selected + $td.data('filter', value.length > 0); + table.draw(); // redraw table, and filters will be applied + } + }, data.filterSettings.select)); + filter[0].selectize.on('blur', function() { + $x.hide().trigger('hide'); $input.parent().show(); $input.trigger('blur'); + }); + filter.next('div').css('margin-bottom', 'auto'); + } else if (type === 'character') { + var fun = function() { + searchColumn(i, $input.val()).draw(); + }; + // throttle searching for server-side processing + var throttledFun = $.fn.dataTable.util.throttle(fun, options.searchDelay); + $input.on('input', function(e, immediate) { + // always bypass throttling when immediate = true (via the updateSearch method) + (immediate || !server) ? fun() : throttledFun(); + }); + } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { + var $x0 = $x; + $x = $x0.children('div').first(); + $x0.css({ + 'background-color': '#fff', + 'border': '1px #ddd solid', + 'border-radius': '4px', + 'padding': data.vertical ? '35px 20px': '20px 20px 10px 20px' + }); + var $spans = $x0.children('span').css({ + 'margin-top': data.vertical ? '0' : '10px', + 'white-space': 'nowrap' + }); + var $span1 = $spans.first(), $span2 = $spans.last(); + var r1 = +$x.data('min'), r2 = +$x.data('max'); + // when the numbers are too small or have many decimal places, the + // slider may have numeric precision problems (#150) + var scale = Math.pow(10, Math.max(0, +$x.data('scale') || 0)); + r1 = Math.round(r1 * scale); r2 = Math.round(r2 * scale); + var scaleBack = function(x, scale) { + if (scale === 1) return x; + var d = Math.round(Math.log(scale) / Math.log(10)); + // to avoid problems like 3.423/100 -> 0.034230000000000003 + return (x / scale).toFixed(d); + }; + var slider_min = function() { + return filter.noUiSlider('options').range.min; + }; + var slider_max = function() { + return filter.noUiSlider('options').range.max; + }; + $input.on({ + focus: function() { + $x0.show().trigger('show'); + // first, make sure the slider div leaves at least 20px between + // the two (slider value) span's + $x0.width(Math.max(160, $span1.outerWidth() + $span2.outerWidth() + 20)); + // then, if the input is really wide or slider is vertical, + // make the slider the same width as the input + if ($x0.outerWidth() < $input.outerWidth() || data.vertical) { + $x0.outerWidth($input.outerWidth()); + } + // make sure the slider div does not reach beyond the right margin + if ($(window).width() < $x0.offset().left + $x0.width()) { + $x0.offset({ + 'left': $input.offset().left + $input.outerWidth() - $x0.outerWidth() + }); + } + }, + blur: function() { + $x0.hide().trigger('hide'); + }, + input: function() { + if ($input.val() === '') filter.val([slider_min(), slider_max()]); + }, + change: function() { + var v = $input.val().replace(/\s/g, ''); + if (v === '') return; + v = v.split('...'); + if (v.length !== 2) { + $input.parent().addClass('has-error'); + return; + } + if (v[0] === '') v[0] = slider_min(); + if (v[1] === '') v[1] = slider_max(); + $input.parent().removeClass('has-error'); + // treat date as UTC time at midnight + var strTime = function(x) { + var s = type === 'date' ? 'T00:00:00Z' : ''; + var t = new Date(x + s).getTime(); + // add 10 minutes to date since it does not hurt the date, and + // it helps avoid the tricky floating point arithmetic problems, + // e.g. sometimes the date may be a few milliseconds earlier + // than the midnight due to precision problems in noUiSlider + return type === 'date' ? t + 3600000 : t; + }; + if (inArray(type, ['date', 'time'])) { + v[0] = strTime(v[0]); + v[1] = strTime(v[1]); + } + if (v[0] != slider_min()) v[0] *= scale; + if (v[1] != slider_max()) v[1] *= scale; + filter.val(v); + } + }); + var formatDate = function(d) { + d = scaleBack(d, scale); + if (type === 'number') return d; + if (type === 'integer') return parseInt(d); + var x = new Date(+d); + if (type === 'date') { + var pad0 = function(x) { + return ('0' + x).substr(-2, 2); + }; + return x.getUTCFullYear() + '-' + pad0(1 + x.getUTCMonth()) + + '-' + pad0(x.getUTCDate()); + } else { + return x.toISOString(); + } + }; + var opts = type === 'date' ? { step: 60 * 60 * 1000 } : + type === 'integer' ? { step: 1 } : {}; + + opts.orientation = data.vertical ? 'vertical': 'horizontal'; + opts.direction = data.vertical ? 'rtl': 'ltr'; + + filter = $x.noUiSlider($.extend({ + start: [r1, r2], + range: {min: r1, max: r2}, + connect: true + }, opts, data.filterSettings.slider)); + if (scale > 1) (function() { + var t1 = r1, t2 = r2; + var val = filter.val(); + while (val[0] > r1 || val[1] < r2) { + if (val[0] > r1) { + t1 -= val[0] - r1; + } + if (val[1] < r2) { + t2 += r2 - val[1]; + } + filter = $x.noUiSlider($.extend({ + start: [t1, t2], + range: {min: t1, max: t2}, + connect: true + }, opts, data.filterSettings.slider), true); + val = filter.val(); + } + r1 = t1; r2 = t2; + })(); + // format with active column renderer, if defined + var colDef = data.options.columnDefs.find(function(def) { + return (def.targets === i || inArray(i, def.targets)) && 'render' in def; + }); + var updateSliderText = function(v1, v2) { + // we only know how to use function renderers + if (colDef && typeof colDef.render === 'function') { + var restore = function(v) { + v = scaleBack(v, scale); + return inArray(type, ['date', 'time']) ? new Date(+v) : v; + } + $span1.text(colDef.render(restore(v1), 'display')); + $span2.text(colDef.render(restore(v2), 'display')); + } else { + $span1.text(formatDate(v1)); + $span2.text(formatDate(v2)); + } + }; + updateSliderText(r1, r2); + var updateSlider = function(e) { + var val = filter.val(); + // turn off filter if in full range + $td.data('filter', val[0] > slider_min() || val[1] < slider_max()); + var v1 = formatDate(val[0]), v2 = formatDate(val[1]), ival; + if ($td.data('filter')) { + ival = v1 + ' ... ' + v2; + $input.attr('title', ival).val(ival).trigger('input'); + } else { + $input.attr('title', '').val(''); + } + updateSliderText(val[0], val[1]); + if (e.type === 'slide') return; // no searching when sliding only + if (server) { + searchColumn(i, $td.data('filter') ? ival : '').draw(); + return; + } + table.draw(); + }; + filter.on({ + set: updateSlider, + slide: updateSlider + }); + } + + // server-side processing will be handled by R (or whatever server + // language you use); the following code is only needed for client-side + // processing + if (server) { + // if a search string has been pre-set, search now + if (searchCol) $input.trigger('input').trigger('change'); + return; + } + + var customFilter = function(settings, data, dataIndex) { + // there is no way to attach a search function to a specific table, + // and we need to make sure a global search function is not applied to + // all tables (i.e. a range filter in a previous table should not be + // applied to the current table); we use the settings object to + // determine if we want to perform searching on the current table, + // since settings.sTableId will be different to different tables + if (table.settings()[0] !== settings) return true; + // no filter on this column or no need to filter this column + if (typeof filter === 'undefined' || !$td.data('filter')) return true; + + var r = filter.val(), v, r0, r1; + var i_data = function(i) { + if (!colReorderEnabled()) return i; + var order = table.colReorder.order(), k; + for (k = 0; k < order.length; ++k) if (order[k] === i) return k; + return i; // in theory it will never be here... + } + v = data[i_data(i)]; + if (type === 'number' || type === 'integer') { + v = parseFloat(v); + // how to handle NaN? currently exclude these rows + if (isNaN(v)) return(false); + r0 = parseFloat(scaleBack(r[0], scale)) + r1 = parseFloat(scaleBack(r[1], scale)); + if (v >= r0 && v <= r1) return true; + } else if (type === 'date' || type === 'time') { + v = new Date(v); + r0 = new Date(r[0] / scale); r1 = new Date(r[1] / scale); + if (v >= r0 && v <= r1) return true; + } else if (type === 'factor') { + if (r.length === 0 || inArray(v, r)) return true; + } else if (type === 'logical') { + if (r.length === 0) return true; + if (inArray(v === '' ? 'na' : v, r)) return true; + } + return false; + }; + + $.fn.dataTable.ext.search.push(customFilter); + + // search for the preset search strings if it is non-empty + if (searchCol) $input.trigger('input').trigger('change'); + + }); + + } + + // highlight search keywords + var highlight = function() { + var body = $(table.table().body()); + // removing the old highlighting first + body.unhighlight(); + + // don't highlight the "not found" row, so we get the rows using the api + if (table.rows({ filter: 'applied' }).data().length === 0) return; + // highlight global search keywords + body.highlight($.trim(table.search()).split(/\s+/)); + // then highlight keywords from individual column filters + if (filterRow) filterRow.each(function(i, td) { + var $td = $(td), type = $td.data('type'); + if (type !== 'character') return; + var $input = $td.children('div').first().children('input'); + var column = table.column(i).nodes().to$(), + val = $.trim($input.val()); + if (type !== 'character' || val === '') return; + column.highlight(val.split(/\s+/)); + }); + }; + + if (options.searchHighlight) { + table + .on('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth', highlight) + .on('destroy', function() { + // remove event handler + table.off('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth'); + }); + + // Set the option for escaping regex characters in our search string. This will be used + // for all future matching. + jQuery.fn.highlight.options.escapeRegex = (!options.search || !options.search.regex); + + // initial highlight for state saved conditions and initial states + highlight(); + } + + // run the callback function on the table instance + if (typeof data.callback === 'function') data.callback(table); + + // double click to edit the cell, row, column, or all cells + if (data.editable) table.on('dblclick.dt', 'tbody td', function(e) { + // only bring up the editor when the cell itself is dbclicked, and ignore + // other dbclick events bubbled up (e.g. from the ) + if (e.target !== this) return; + var target = [], immediate = false; + switch (data.editable.target) { + case 'cell': + target = [this]; + immediate = true; // edit will take effect immediately + break; + case 'row': + target = table.cells(table.cell(this).index().row, '*').nodes(); + break; + case 'column': + target = table.cells('*', table.cell(this).index().column).nodes(); + break; + case 'all': + target = table.cells().nodes(); + break; + default: + throw 'The editable parameter must be "cell", "row", "column", or "all"'; + } + var disableCols = data.editable.disable ? data.editable.disable.columns : null; + var numericCols = data.editable.numeric; + var areaCols = data.editable.area; + var dateCols = data.editable.date; + for (var i = 0; i < target.length; i++) { + (function(cell, current) { + var $cell = $(cell), html = $cell.html(); + var _cell = table.cell(cell), value = _cell.data(), index = _cell.index().column; + var $input; + if (inArray(index, numericCols)) { + $input = $(''); + } else if (inArray(index, areaCols)) { + $input = $(''); + } else if (inArray(index, dateCols)) { + $input = $(''); + } else { + $input = $(''); + } + if (!immediate) { + $cell.data('input', $input).data('html', html); + $input.attr('title', 'Hit Ctrl+Enter to finish editing, or Esc to cancel'); + } + $input.val(value); + if (inArray(index, disableCols)) { + $input.attr('readonly', '').css('filter', 'invert(25%)'); + } + $cell.empty().append($input); + if (cell === current) $input.focus(); + $input.css('width', '100%'); + + if (immediate) $input.on('blur', function(e) { + var valueNew = $input.val(); + if (valueNew !== value) { + _cell.data(valueNew); + if (HTMLWidgets.shinyMode) { + changeInput('cell_edit', [cellInfo(cell)], 'DT.cellInfo', null, {priority: 'event'}); + } + // for server-side processing, users have to call replaceData() to update the table + if (!server) table.draw(false); + } else { + $cell.html(html); + } + }).on('keyup', function(e) { + // hit Escape to cancel editing + if (e.keyCode === 27) $input.trigger('blur'); + }); + + // bulk edit (row, column, or all) + if (!immediate) $input.on('keyup', function(e) { + var removeInput = function($cell, restore) { + $cell.data('input').remove(); + if (restore) $cell.html($cell.data('html')); + } + if (e.keyCode === 27) { + for (var i = 0; i < target.length; i++) { + removeInput($(target[i]), true); + } + } else if (e.keyCode === 13 && e.ctrlKey) { + // Ctrl + Enter + var cell, $cell, _cell, cellData = []; + for (var i = 0; i < target.length; i++) { + cell = target[i]; $cell = $(cell); _cell = table.cell(cell); + _cell.data($cell.data('input').val()); + HTMLWidgets.shinyMode && cellData.push(cellInfo(cell)); + removeInput($cell, false); + } + if (HTMLWidgets.shinyMode) { + changeInput('cell_edit', cellData, 'DT.cellInfo', null, {priority: "event"}); + } + if (!server) table.draw(false); + } + }); + })(target[i], this); + } + }); + + // interaction with shiny + if (!HTMLWidgets.shinyMode && !crosstalkOptions.group) return; + + var methods = {}; + var shinyData = {}; + + methods.updateCaption = function(caption) { + if (!caption) return; + $table.children('caption').replaceWith(caption); + } + + // register clear functions to remove input values when the table is removed + instance.clearInputs = {}; + + var changeInput = function(id, value, type, noCrosstalk, opts) { + var event = id; + id = el.id + '_' + id; + if (type) id = id + ':' + type; + // do not update if the new value is the same as old value + if (event !== 'cell_edit' && !/_clicked$/.test(event) && shinyData.hasOwnProperty(id) && shinyData[id] === JSON.stringify(value)) + return; + shinyData[id] = JSON.stringify(value); + if (HTMLWidgets.shinyMode && Shiny.setInputValue) { + Shiny.setInputValue(id, value, opts); + if (!instance.clearInputs[id]) instance.clearInputs[id] = function() { + Shiny.setInputValue(id, null); + } + } + + // HACK + if (event === "rows_selected" && !noCrosstalk) { + if (crosstalkOptions.group) { + var keys = crosstalkOptions.key; + var selectedKeys = null; + if (value) { + selectedKeys = []; + for (var i = 0; i < value.length; i++) { + // The value array's contents use 1-based row numbers, so we must + // convert to 0-based before indexing into the keys array. + selectedKeys.push(keys[value[i] - 1]); + } + } + instance.ctselectHandle.set(selectedKeys); + } + } + }; + + var addOne = function(x) { + return x.map(function(i) { return 1 + i; }); + }; + + var unique = function(x) { + var ux = []; + $.each(x, function(i, el){ + if ($.inArray(el, ux) === -1) ux.push(el); + }); + return ux; + } + + // change the row index of a cell + var tweakCellIndex = function(cell) { + var info = cell.index(); + // some cell may not be valid. e.g, #759 + // when using the RowGroup extension, datatables will + // generate the row label and the cells are not part of + // the data thus contain no row/col info + if (info === undefined) + return {row: null, col: null}; + if (server) { + info.row = DT_rows_current[info.row]; + } else { + info.row += 1; + } + return {row: info.row, col: info.column}; + } + + var cleanSelectedValues = function() { + changeInput('rows_selected', []); + changeInput('columns_selected', []); + changeInput('cells_selected', transposeArray2D([]), 'shiny.matrix'); + } + // #828 we should clean the selection on the server-side when the table reloads + cleanSelectedValues(); + + // a flag to indicates if select extension is initialized or not + var flagSelectExt = table.settings()[0]._select !== undefined; + // the Select extension should only be used in the client mode and + // when the selection.mode is set to none + if (data.selection.mode === 'none' && !server && flagSelectExt) { + var updateRowsSelected = function() { + var rows = table.rows({selected: true}); + var selected = []; + $.each(rows.indexes().toArray(), function(i, v) { + selected.push(v + 1); + }); + changeInput('rows_selected', selected); + } + var updateColsSelected = function() { + var columns = table.columns({selected: true}); + changeInput('columns_selected', columns.indexes().toArray()); + } + var updateCellsSelected = function() { + var cells = table.cells({selected: true}); + var selected = []; + cells.every(function() { + var row = this.index().row; + var col = this.index().column; + selected = selected.concat([[row + 1, col]]); + }); + changeInput('cells_selected', transposeArray2D(selected), 'shiny.matrix'); + } + table.on('select deselect', function(e, dt, type, indexes) { + updateRowsSelected(); + updateColsSelected(); + updateCellsSelected(); + }) + updateRowsSelected(); + updateColsSelected(); + updateCellsSelected(); + } + + var selMode = data.selection.mode, selTarget = data.selection.target; + var selDisable = data.selection.selectable === false; + if (inArray(selMode, ['single', 'multiple'])) { + var selClass = inArray(data.style, ['bootstrap', 'bootstrap4']) ? 'active' : 'selected'; + // selected1: row indices; selected2: column indices + var initSel = function(x) { + if (x === null || typeof x === 'boolean' || selTarget === 'cell') { + return {rows: [], cols: []}; + } else if (selTarget === 'row') { + return {rows: $.makeArray(x), cols: []}; + } else if (selTarget === 'column') { + return {rows: [], cols: $.makeArray(x)}; + } else if (selTarget === 'row+column') { + return {rows: $.makeArray(x.rows), cols: $.makeArray(x.cols)}; + } + } + var selected = data.selection.selected; + var selected1 = initSel(selected).rows, selected2 = initSel(selected).cols; + // selectable should contain either all positive or all non-positive values, not both + // positive values indicate "selectable" while non-positive values means "nonselectable" + // the assertion is performed on R side. (only column indicides could be zero which indicates + // the row name) + var selectable = data.selection.selectable; + var selectable1 = initSel(selectable).rows, selectable2 = initSel(selectable).cols; + + // After users reorder the rows or filter the table, we cannot use the table index + // directly. Instead, we need this function to find out the rows between the two clicks. + // If user filter the table again between the start click and the end click, the behavior + // would be undefined, but it should not be a problem. + var shiftSelRowsIndex = function(start, end) { + var indexes = server ? DT_rows_all : table.rows({ search: 'applied' }).indexes().toArray(); + start = indexes.indexOf(start); end = indexes.indexOf(end); + // if start is larger than end, we need to swap + if (start > end) { + var tmp = end; end = start; start = tmp; + } + return indexes.slice(start, end + 1); + } + + var serverRowIndex = function(clientRowIndex) { + return server ? DT_rows_current[clientRowIndex] : clientRowIndex + 1; + } + + // row, column, or cell selection + var lastClickedRow; + if (inArray(selTarget, ['row', 'row+column'])) { + // Get the current selected rows. It will also + // update the selected1's value based on the current row selection state + // Note we can't put this function inside selectRows() directly, + // the reason is method.selectRows() will override selected1's value but this + // function will add rows to selected1 (keep the existing selection), which is + // inconsistent with column and cell selection. + var selectedRows = function() { + var rows = table.rows('.' + selClass); + var idx = rows.indexes().toArray(); + if (!server) { + selected1 = addOne(idx); + return selected1; + } + idx = idx.map(function(i) { + return DT_rows_current[i]; + }); + selected1 = selMode === 'multiple' ? unique(selected1.concat(idx)) : idx; + return selected1; + } + // Change selected1's value based on selectable1, then refresh the row state + var onlyKeepSelectableRows = function() { + if (selDisable) { // users can't select; useful when only want backend select + selected1 = []; + return; + } + if (selectable1.length === 0) return; + var nonselectable = selectable1[0] <= 0; + if (nonselectable) { + // should make selectable1 positive + selected1 = $(selected1).not(selectable1.map(function(i) { return -i; })).get(); + } else { + selected1 = $(selected1).filter(selectable1).get(); + } + } + // Change selected1's value based on selectable1, then + // refresh the row selection state according to values in selected1 + var selectRows = function(ignoreSelectable) { + if (!ignoreSelectable) onlyKeepSelectableRows(); + table.$('tr.' + selClass).removeClass(selClass); + if (selected1.length === 0) return; + if (server) { + table.rows({page: 'current'}).every(function() { + if (inArray(DT_rows_current[this.index()], selected1)) { + $(this.node()).addClass(selClass); + } + }); + } else { + var selected0 = selected1.map(function(i) { return i - 1; }); + $(table.rows(selected0).nodes()).addClass(selClass); + } + } + table.on('mousedown.dt', 'tbody tr', function(e) { + var $this = $(this), thisRow = table.row(this); + if (selMode === 'multiple') { + if (e.shiftKey && lastClickedRow !== undefined) { + // select or de-select depends on the last clicked row's status + var flagSel = !$this.hasClass(selClass); + var crtClickedRow = serverRowIndex(thisRow.index()); + if (server) { + var rowsIndex = shiftSelRowsIndex(lastClickedRow, crtClickedRow); + // update current page's selClass + rowsIndex.map(function(i) { + var rowIndex = DT_rows_current.indexOf(i); + if (rowIndex >= 0) { + var row = table.row(rowIndex).nodes().to$(); + var flagRowSel = !row.hasClass(selClass); + if (flagSel === flagRowSel) row.toggleClass(selClass); + } + }); + // update selected1 + if (flagSel) { + selected1 = unique(selected1.concat(rowsIndex)); + } else { + selected1 = selected1.filter(function(index) { + return !inArray(index, rowsIndex); + }); + } + } else { + // js starts from 0 + shiftSelRowsIndex(lastClickedRow - 1, crtClickedRow - 1).map(function(value) { + var row = table.row(value).nodes().to$(); + var flagRowSel = !row.hasClass(selClass); + if (flagSel === flagRowSel) row.toggleClass(selClass); + }); + } + e.preventDefault(); + } else { + $this.toggleClass(selClass); + } + } else { + if ($this.hasClass(selClass)) { + $this.removeClass(selClass); + } else { + table.$('tr.' + selClass).removeClass(selClass); + $this.addClass(selClass); + } + } + if (server && !$this.hasClass(selClass)) { + var id = DT_rows_current[thisRow.index()]; + // remove id from selected1 since its class .selected has been removed + if (inArray(id, selected1)) selected1.splice($.inArray(id, selected1), 1); + } + selectedRows(); // update selected1's value based on selClass + selectRows(false); // only keep the selectable rows + changeInput('rows_selected', selected1); + changeInput('row_last_clicked', serverRowIndex(thisRow.index()), null, null, {priority: 'event'}); + lastClickedRow = serverRowIndex(thisRow.index()); + }); + selectRows(false); // in case users have specified pre-selected rows + // restore selected rows after the table is redrawn (e.g. sort/search/page); + // client-side tables will preserve the selections automatically; for + // server-side tables, we have to *real* row indices are in `selected1` + changeInput('rows_selected', selected1); + if (server) table.on('draw.dt', function(e) { selectRows(false); }); + methods.selectRows = function(selected, ignoreSelectable) { + selected1 = $.makeArray(selected); + selectRows(ignoreSelectable); + changeInput('rows_selected', selected1); + } + } + + if (inArray(selTarget, ['column', 'row+column'])) { + if (selTarget === 'row+column') { + $(table.columns().footer()).css('cursor', 'pointer'); + } + // update selected2's value based on selectable2 + var onlyKeepSelectableCols = function() { + if (selDisable) { // users can't select; useful when only want backend select + selected2 = []; + return; + } + if (selectable2.length === 0) return; + var nonselectable = selectable2[0] <= 0; + if (nonselectable) { + // need to make selectable2 positive + selected2 = $(selected2).not(selectable2.map(function(i) { return -i; })).get(); + } else { + selected2 = $(selected2).filter(selectable2).get(); + } + } + // update selected2 and then + // refresh the col selection state according to values in selected2 + var selectCols = function(ignoreSelectable) { + if (!ignoreSelectable) onlyKeepSelectableCols(); + // if selected2 is not a valide index (e.g., larger than the column number) + // table.columns(selected2) will fail and result in a blank table + // this is different from the table.rows(), where the out-of-range indexes + // doesn't affect at all + selected2 = $(selected2).filter(table.columns().indexes()).get(); + table.columns().nodes().flatten().to$().removeClass(selClass); + if (selected2.length > 0) + table.columns(selected2).nodes().flatten().to$().addClass(selClass); + } + var callback = function() { + var colIdx = selTarget === 'column' ? table.cell(this).index().column : + $.inArray(this, table.columns().footer()), + thisCol = $(table.column(colIdx).nodes()); + if (colIdx === -1) return; + if (thisCol.hasClass(selClass)) { + thisCol.removeClass(selClass); + selected2.splice($.inArray(colIdx, selected2), 1); + } else { + if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); + thisCol.addClass(selClass); + selected2 = selMode === 'single' ? [colIdx] : unique(selected2.concat([colIdx])); + } + selectCols(false); // update selected2 based on selectable + changeInput('columns_selected', selected2); + } + if (selTarget === 'column') { + $(table.table().body()).on('click.dt', 'td', callback); + } else { + $(table.table().footer()).on('click.dt', 'tr th', callback); + } + selectCols(false); // in case users have specified pre-selected columns + changeInput('columns_selected', selected2); + if (server) table.on('draw.dt', function(e) { selectCols(false); }); + methods.selectColumns = function(selected, ignoreSelectable) { + selected2 = $.makeArray(selected); + selectCols(ignoreSelectable); + changeInput('columns_selected', selected2); + } + } + + if (selTarget === 'cell') { + var selected3 = [], selectable3 = []; + if (selected !== null) selected3 = selected; + if (selectable !== null && typeof selectable !== 'boolean') selectable3 = selectable; + var findIndex = function(ij, sel) { + for (var i = 0; i < sel.length; i++) { + if (ij[0] === sel[i][0] && ij[1] === sel[i][1]) return i; + } + return -1; + } + // Change selected3's value based on selectable3, then refresh the cell state + var onlyKeepSelectableCells = function() { + if (selDisable) { // users can't select; useful when only want backend select + selected3 = []; + return; + } + if (selectable3.length === 0) return; + var nonselectable = selectable3[0][0] <= 0; + var out = []; + if (nonselectable) { + selected3.map(function(ij) { + // should make selectable3 positive + if (findIndex([-ij[0], -ij[1]], selectable3) === -1) { out.push(ij); } + }); + } else { + selected3.map(function(ij) { + if (findIndex(ij, selectable3) > -1) { out.push(ij); } + }); + } + selected3 = out; + } + // Change selected3's value based on selectable3, then + // refresh the cell selection state according to values in selected3 + var selectCells = function(ignoreSelectable) { + if (!ignoreSelectable) onlyKeepSelectableCells(); + table.$('td.' + selClass).removeClass(selClass); + if (selected3.length === 0) return; + if (server) { + table.cells({page: 'current'}).every(function() { + var info = tweakCellIndex(this); + if (findIndex([info.row, info.col], selected3) > -1) + $(this.node()).addClass(selClass); + }); + } else { + selected3.map(function(ij) { + $(table.cell(ij[0] - 1, ij[1]).node()).addClass(selClass); + }); + } + }; + table.on('click.dt', 'tbody td', function() { + var $this = $(this), info = tweakCellIndex(table.cell(this)); + if ($this.hasClass(selClass)) { + $this.removeClass(selClass); + selected3.splice(findIndex([info.row, info.col], selected3), 1); + } else { + if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); + $this.addClass(selClass); + selected3 = selMode === 'single' ? [[info.row, info.col]] : + unique(selected3.concat([[info.row, info.col]])); + } + selectCells(false); // must call this to update selected3 based on selectable3 + changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); + }); + selectCells(false); // in case users have specified pre-selected columns + changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); + + if (server) table.on('draw.dt', function(e) { selectCells(false); }); + methods.selectCells = function(selected, ignoreSelectable) { + selected3 = selected ? selected : []; + selectCells(ignoreSelectable); + changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); + } + } + } + + // expose some table info to Shiny + var updateTableInfo = function(e, settings) { + // TODO: is anyone interested in the page info? + // changeInput('page_info', table.page.info()); + var updateRowInfo = function(id, modifier) { + var idx; + if (server) { + idx = modifier.page === 'current' ? DT_rows_current : DT_rows_all; + } else { + var rows = table.rows($.extend({ + search: 'applied', + page: 'all' + }, modifier)); + idx = addOne(rows.indexes().toArray()); + } + changeInput('rows' + '_' + id, idx); + }; + updateRowInfo('current', {page: 'current'}); + updateRowInfo('all', {}); + } + table.on('draw.dt', updateTableInfo); + updateTableInfo(); + + // state info + table.on('draw.dt column-visibility.dt', function() { + changeInput('state', table.state()); + }); + changeInput('state', table.state()); + + // search info + var updateSearchInfo = function() { + changeInput('search', table.search()); + if (filterRow) changeInput('search_columns', filterRow.toArray().map(function(td) { + return $(td).find('input').first().val(); + })); + } + table.on('draw.dt', updateSearchInfo); + updateSearchInfo(); + + var cellInfo = function(thiz) { + var info = tweakCellIndex(table.cell(thiz)); + info.value = table.cell(thiz).data(); + return info; + } + // the current cell clicked on + table.on('click.dt', 'tbody td', function() { + changeInput('cell_clicked', cellInfo(this), null, null, {priority: 'event'}); + }) + changeInput('cell_clicked', {}); + + // do not trigger table selection when clicking on links unless they have classes + table.on('mousedown.dt', 'tbody td a', function(e) { + if (this.className === '') e.stopPropagation(); + }); + + methods.addRow = function(data, rowname, resetPaging) { + var n = table.columns().indexes().length, d = n - data.length; + if (d === 1) { + data = rowname.concat(data) + } else if (d !== 0) { + console.log(data); + console.log(table.columns().indexes()); + throw 'New data must be of the same length as current data (' + n + ')'; + }; + table.row.add(data).draw(resetPaging); + } + + methods.updateSearch = function(keywords) { + if (keywords.global !== null) + $(table.table().container()).find('input[type=search]').first() + .val(keywords.global).trigger('input'); + var columns = keywords.columns; + if (!filterRow || columns === null) return; + filterRow.toArray().map(function(td, i) { + var v = typeof columns === 'string' ? columns : columns[i]; + if (typeof v === 'undefined') { + console.log('The search keyword for column ' + i + ' is undefined') + return; + } + // Update column search string and values on linked filter widgets. + // 'input' for factor and char filters, 'change' for numeric filters. + $(td).find('input').first().val(v).trigger('input', [true]).trigger('change'); + }); + table.draw(); + } + + methods.hideCols = function(hide, reset) { + if (reset) table.columns().visible(true, false); + table.columns(hide).visible(false); + } + + methods.showCols = function(show, reset) { + if (reset) table.columns().visible(false, false); + table.columns(show).visible(true); + } + + methods.colReorder = function(order, origOrder) { + table.colReorder.order(order, origOrder); + } + + methods.selectPage = function(page) { + if (table.page.info().pages < page || page < 1) { + throw 'Selected page is out of range'; + }; + table.page(page - 1).draw(false); + } + + methods.reloadData = function(resetPaging, clearSelection) { + // empty selections first if necessary + if (methods.selectRows && inArray('row', clearSelection)) methods.selectRows([]); + if (methods.selectColumns && inArray('column', clearSelection)) methods.selectColumns([]); + if (methods.selectCells && inArray('cell', clearSelection)) methods.selectCells([]); + table.ajax.reload(null, resetPaging); + } + + // update table filters (set new limits of sliders) + methods.updateFilters = function(newProps) { + // loop through each filter in the filter row + filterRow.each(function(i, td) { + var k = i; + if (filterRow.length > newProps.length) { + if (i === 0) return; // first column is row names + k = i - 1; + } + // Update the filters to reflect the updated data. + // Allow "falsy" (e.g. NULL) to signify a no-op. + if (newProps[k]) { + setFilterProps(td, newProps[k]); + } + }); + }; + + table.shinyMethods = methods; + }, + resize: function(el, width, height, instance) { + if (instance.data) this.renderValue(el, instance.data, instance); + + // dynamically adjust height if fillContainer = TRUE + if (instance.fillContainer) + this.fillAvailableHeight(el, height); + + this.adjustWidth(el); + }, + + // dynamically set the scroll body to fill available height + // (used with fillContainer = TRUE) + fillAvailableHeight: function(el, availableHeight) { + + // see how much of the table is occupied by header/footer elements + // and use that to compute a target scroll body height + var dtWrapper = $(el).find('div.dataTables_wrapper'); + var dtScrollBody = $(el).find($('div.dataTables_scrollBody')); + var framingHeight = dtWrapper.innerHeight() - dtScrollBody.innerHeight(); + var scrollBodyHeight = availableHeight - framingHeight; + + // we need to set `max-height` to none as datatables library now sets this + // to a fixed height, disabling the ability to resize to fill the window, + // as it will be set to a fixed 100px under such circumstances, e.g., RStudio IDE, + // or FlexDashboard + // see https://github.com/rstudio/DT/issues/951#issuecomment-1026464509 + dtScrollBody.css('max-height', 'none'); + // set the height + dtScrollBody.height(scrollBodyHeight + 'px'); + }, + + // adjust the width of columns; remove the hard-coded widths on table and the + // scroll header when scrollX/Y are enabled + adjustWidth: function(el) { + var $el = $(el), table = $el.data('datatable'); + if (table) table.columns.adjust(); + $el.find('.dataTables_scrollHeadInner').css('width', '') + .children('table').css('margin-left', ''); + } +}); + + if (!HTMLWidgets.shinyMode) return; + + Shiny.addCustomMessageHandler('datatable-calls', function(data) { + var id = data.id; + var el = document.getElementById(id); + var table = el ? $(el).data('datatable') : null; + if (!table) { + console.log("Couldn't find table with id " + id); + return; + } + + var methods = table.shinyMethods, call = data.call; + if (methods[call.method]) { + methods[call.method].apply(table, call.args); + } else { + console.log("Unknown method " + call.method); + } + }); + +})(); diff --git a/html_outputs/site_libs/htmltools-fill-0.5.8.1/fill.css b/html_outputs/site_libs/htmltools-fill-0.5.8.1/fill.css new file mode 100644 index 00000000..841ea9d5 --- /dev/null +++ b/html_outputs/site_libs/htmltools-fill-0.5.8.1/fill.css @@ -0,0 +1,21 @@ +@layer htmltools { + .html-fill-container { + display: flex; + flex-direction: column; + /* Prevent the container from expanding vertically or horizontally beyond its + parent's constraints. */ + min-height: 0; + min-width: 0; + } + .html-fill-container > .html-fill-item { + /* Fill items can grow and shrink freely within + available vertical space in fillable container */ + flex: 1 1 auto; + min-height: 0; + min-width: 0; + } + .html-fill-container > :not(.html-fill-item) { + /* Prevent shrinking or growing of non-fill items */ + flex: 0 0 auto; + } +} diff --git a/html_outputs/site_libs/leaflet-binding-2.2.2/leaflet.js b/html_outputs/site_libs/leaflet-binding-2.2.2/leaflet.js new file mode 100644 index 00000000..79dbe714 --- /dev/null +++ b/html_outputs/site_libs/leaflet-binding-2.2.2/leaflet.js @@ -0,0 +1,2787 @@ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i this.effectiveLength) throw new Error("Row argument was out of bounds: " + row + " > " + this.effectiveLength); + var colIndex = -1; + + if (typeof col === "undefined") { + var rowData = {}; + this.colnames.forEach(function (name, i) { + rowData[name] = _this3.columns[i][row % _this3.columns[i].length]; + }); + return rowData; + } else if (typeof col === "string") { + colIndex = this._colIndex(col); + } else if (typeof col === "number") { + colIndex = col; + } + + if (colIndex < 0 || colIndex > this.columns.length) { + if (missingOK) return void 0;else throw new Error("Unknown column index: " + col); + } + + return this.columns[colIndex][row % this.columns[colIndex].length]; + } + }, { + key: "nrow", + value: function nrow() { + return this.effectiveLength; + } + }]); + + return DataFrame; +}(); + +exports["default"] = DataFrame; + + +},{"./util":17}],5:[function(require,module,exports){ +"use strict"; + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +// In RMarkdown's self-contained mode, we don't have a way to carry around the +// images that Leaflet needs but doesn't load into the page. Instead, we'll use +// the unpkg CDN. +if (typeof _leaflet2["default"].Icon.Default.imagePath === "undefined") { + _leaflet2["default"].Icon.Default.imagePath = "https://unpkg.com/leaflet@1.3.1/dist/images/"; +} + + +},{"./global/leaflet":10}],6:[function(require,module,exports){ +"use strict"; + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +// add texxtsize, textOnly, and style +_leaflet2["default"].Tooltip.prototype.options.textsize = "10px"; +_leaflet2["default"].Tooltip.prototype.options.textOnly = false; +_leaflet2["default"].Tooltip.prototype.options.style = null; // copy original layout to not completely stomp it. + +var initLayoutOriginal = _leaflet2["default"].Tooltip.prototype._initLayout; + +_leaflet2["default"].Tooltip.prototype._initLayout = function () { + initLayoutOriginal.call(this); + this._container.style.fontSize = this.options.textsize; + + if (this.options.textOnly) { + _leaflet2["default"].DomUtil.addClass(this._container, "leaflet-tooltip-text-only"); + } + + if (this.options.style) { + for (var property in this.options.style) { + this._container.style[property] = this.options.style[property]; + } + } +}; + + +},{"./global/leaflet":10}],7:[function(require,module,exports){ +"use strict"; + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var protocolRegex = /^\/\//; + +var upgrade_protocol = function upgrade_protocol(urlTemplate) { + if (protocolRegex.test(urlTemplate)) { + if (window.location.protocol === "file:") { + // if in a local file, support http + // http should auto upgrade if necessary + urlTemplate = "http:" + urlTemplate; + } + } + + return urlTemplate; +}; + +var originalLTileLayerInitialize = _leaflet2["default"].TileLayer.prototype.initialize; + +_leaflet2["default"].TileLayer.prototype.initialize = function (urlTemplate, options) { + urlTemplate = upgrade_protocol(urlTemplate); + originalLTileLayerInitialize.call(this, urlTemplate, options); +}; + +var originalLTileLayerWMSInitialize = _leaflet2["default"].TileLayer.WMS.prototype.initialize; + +_leaflet2["default"].TileLayer.WMS.prototype.initialize = function (urlTemplate, options) { + urlTemplate = upgrade_protocol(urlTemplate); + originalLTileLayerWMSInitialize.call(this, urlTemplate, options); +}; + + +},{"./global/leaflet":10}],8:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.HTMLWidgets; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],9:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.jQuery; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],10:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.L; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],11:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.L.Proj; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],12:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.Shiny; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],13:[function(require,module,exports){ +"use strict"; + +var _jquery = require("./global/jquery"); + +var _jquery2 = _interopRequireDefault(_jquery); + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +var _shiny = require("./global/shiny"); + +var _shiny2 = _interopRequireDefault(_shiny); + +var _htmlwidgets = require("./global/htmlwidgets"); + +var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); + +var _util = require("./util"); + +var _crs_utils = require("./crs_utils"); + +var _controlStore = require("./control-store"); + +var _controlStore2 = _interopRequireDefault(_controlStore); + +var _layerManager = require("./layer-manager"); + +var _layerManager2 = _interopRequireDefault(_layerManager); + +var _methods = require("./methods"); + +var _methods2 = _interopRequireDefault(_methods); + +require("./fixup-default-icon"); + +require("./fixup-default-tooltip"); + +require("./fixup-url-protocol"); + +var _dataframe = require("./dataframe"); + +var _dataframe2 = _interopRequireDefault(_dataframe); + +var _clusterLayerStore = require("./cluster-layer-store"); + +var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +window.LeafletWidget = {}; +window.LeafletWidget.utils = {}; + +var methods = window.LeafletWidget.methods = _jquery2["default"].extend({}, _methods2["default"]); + +window.LeafletWidget.DataFrame = _dataframe2["default"]; +window.LeafletWidget.ClusterLayerStore = _clusterLayerStore2["default"]; +window.LeafletWidget.utils.getCRS = _crs_utils.getCRS; // Send updated bounds back to app. Takes a leaflet event object as input. + +function updateBounds(map) { + var id = map.getContainer().id; + var bounds = map.getBounds(); + + _shiny2["default"].onInputChange(id + "_bounds", { + north: bounds.getNorthEast().lat, + east: bounds.getNorthEast().lng, + south: bounds.getSouthWest().lat, + west: bounds.getSouthWest().lng + }); + + _shiny2["default"].onInputChange(id + "_center", { + lng: map.getCenter().lng, + lat: map.getCenter().lat + }); + + _shiny2["default"].onInputChange(id + "_zoom", map.getZoom()); +} + +function preventUnintendedZoomOnScroll(map) { + // Prevent unwanted scroll capturing. Similar in purpose to + // https://github.com/CliffCloud/Leaflet.Sleep but with a + // different set of heuristics. + // The basic idea is that when a mousewheel/DOMMouseScroll + // event is seen, we disable scroll wheel zooming until the + // user moves their mouse cursor or clicks on the map. This + // is slightly trickier than just listening for mousemove, + // because mousemove is fired when the page is scrolled, + // even if the user did not physically move the mouse. We + // handle this by examining the mousemove event's screenX + // and screenY properties; if they change, we know it's a + // "true" move. + // lastScreen can never be null, but its x and y can. + var lastScreen = { + x: null, + y: null + }; + (0, _jquery2["default"])(document).on("mousewheel DOMMouseScroll", "*", function (e) { + // Disable zooming (until the mouse moves or click) + map.scrollWheelZoom.disable(); // Any mousemove events at this screen position will be ignored. + + lastScreen = { + x: e.originalEvent.screenX, + y: e.originalEvent.screenY + }; + }); + (0, _jquery2["default"])(document).on("mousemove", "*", function (e) { + // Did the mouse really move? + if (map.options.scrollWheelZoom) { + if (lastScreen.x !== null && e.screenX !== lastScreen.x || e.screenY !== lastScreen.y) { + // It really moved. Enable zooming. + map.scrollWheelZoom.enable(); + lastScreen = { + x: null, + y: null + }; + } + } + }); + (0, _jquery2["default"])(document).on("mousedown", ".leaflet", function (e) { + // Clicking always enables zooming. + if (map.options.scrollWheelZoom) { + map.scrollWheelZoom.enable(); + lastScreen = { + x: null, + y: null + }; + } + }); +} + +_htmlwidgets2["default"].widget({ + name: "leaflet", + type: "output", + factory: function factory(el, width, height) { + var map = null; + return { + // we need to store our map in our returned object. + getMap: function getMap() { + return map; + }, + renderValue: function renderValue(data) { + // Create an appropriate CRS Object if specified + if (data && data.options && data.options.crs) { + data.options.crs = (0, _crs_utils.getCRS)(data.options.crs); + } // As per https://github.com/rstudio/leaflet/pull/294#discussion_r79584810 + + + if (map) { + map.remove(); + + map = function () { + return; + }(); // undefine map + + } + + if (data.options.mapFactory && typeof data.options.mapFactory === "function") { + map = data.options.mapFactory(el, data.options); + } else { + map = _leaflet2["default"].map(el, data.options); + } + + preventUnintendedZoomOnScroll(map); // Store some state in the map object + + map.leafletr = { + // Has the map ever rendered successfully? + hasRendered: false, + // Data to be rendered when resize is called with area != 0 + pendingRenderData: null + }; // Check if the map is rendered statically (no output binding) + + if (_htmlwidgets2["default"].shinyMode && /\bshiny-bound-output\b/.test(el.className)) { + map.id = el.id; // Store the map on the element so we can find it later by ID + + (0, _jquery2["default"])(el).data("leaflet-map", map); // When the map is clicked, send the coordinates back to the app + + map.on("click", function (e) { + _shiny2["default"].onInputChange(map.id + "_click", { + lat: e.latlng.lat, + lng: e.latlng.lng, + ".nonce": Math.random() // Force reactivity if lat/lng hasn't changed + + }); + }); + var groupTimerId = null; + map.on("moveend", function (e) { + updateBounds(e.target); + }).on("layeradd layerremove", function (e) { + // If the layer that's coming or going is a group we created, tell + // the server. + if (map.layerManager.getGroupNameFromLayerGroup(e.layer)) { + // But to avoid chattiness, coalesce events + if (groupTimerId) { + clearTimeout(groupTimerId); + groupTimerId = null; + } + + groupTimerId = setTimeout(function () { + groupTimerId = null; + + _shiny2["default"].onInputChange(map.id + "_groups", map.layerManager.getVisibleGroups()); + }, 100); + } + }); + } + + this.doRenderValue(data, map); + }, + doRenderValue: function doRenderValue(data, map) { + // Leaflet does not behave well when you set up a bunch of layers when + // the map is not visible (width/height == 0). Popups get misaligned + // relative to their owning markers, and the fitBounds calculations + // are off. Therefore we wait until the map is actually showing to + // render the value (we rely on the resize() callback being invoked + // at the appropriate time). + if (el.offsetWidth === 0 || el.offsetHeight === 0) { + map.leafletr.pendingRenderData = data; + return; + } + + map.leafletr.pendingRenderData = null; // Merge data options into defaults + + var options = _jquery2["default"].extend({ + zoomToLimits: "always" + }, data.options); + + if (!map.layerManager) { + map.controls = new _controlStore2["default"](map); + map.layerManager = new _layerManager2["default"](map); + } else { + map.controls.clear(); + map.layerManager.clear(); + } + + var explicitView = false; + + if (data.setView) { + explicitView = true; + map.setView.apply(map, data.setView); + } + + if (data.fitBounds) { + explicitView = true; + methods.fitBounds.apply(map, data.fitBounds); + } + + if (data.flyTo) { + if (!explicitView && !map.leafletr.hasRendered) { + // must be done to give a initial starting point + map.fitWorld(); + } + + explicitView = true; + map.flyTo.apply(map, data.flyTo); + } + + if (data.flyToBounds) { + if (!explicitView && !map.leafletr.hasRendered) { + // must be done to give a initial starting point + map.fitWorld(); + } + + explicitView = true; + methods.flyToBounds.apply(map, data.flyToBounds); + } + + if (data.options.center) { + explicitView = true; + } // Returns true if the zoomToLimits option says that the map should be + // zoomed to map elements. + + + function needsZoom() { + return options.zoomToLimits === "always" || options.zoomToLimits === "first" && !map.leafletr.hasRendered; + } + + if (!explicitView && needsZoom() && !map.getZoom()) { + if (data.limits && !_jquery2["default"].isEmptyObject(data.limits)) { + // Use the natural limits of what's being drawn on the map + // If the size of the bounding box is 0, leaflet gets all weird + var pad = 0.006; + + if (data.limits.lat[0] === data.limits.lat[1]) { + data.limits.lat[0] = data.limits.lat[0] - pad; + data.limits.lat[1] = data.limits.lat[1] + pad; + } + + if (data.limits.lng[0] === data.limits.lng[1]) { + data.limits.lng[0] = data.limits.lng[0] - pad; + data.limits.lng[1] = data.limits.lng[1] + pad; + } + + map.fitBounds([[data.limits.lat[0], data.limits.lng[0]], [data.limits.lat[1], data.limits.lng[1]]]); + } else { + map.fitWorld(); + } + } + + for (var i = 0; data.calls && i < data.calls.length; i++) { + var call = data.calls[i]; + if (methods[call.method]) methods[call.method].apply(map, call.args);else (0, _util.log)("Unknown method " + call.method); + } + + map.leafletr.hasRendered = true; + + if (_htmlwidgets2["default"].shinyMode) { + setTimeout(function () { + updateBounds(map); + }, 1); + } + }, + resize: function resize(width, height) { + if (map) { + map.invalidateSize(); + + if (map.leafletr.pendingRenderData) { + this.doRenderValue(map.leafletr.pendingRenderData, map); + } + } + } + }; + } +}); + +if (_htmlwidgets2["default"].shinyMode) { + _shiny2["default"].addCustomMessageHandler("leaflet-calls", function (data) { + var id = data.id; + var el = document.getElementById(id); + var map = el ? (0, _jquery2["default"])(el).data("leaflet-map") : null; + + if (!map) { + (0, _util.log)("Couldn't find map with id " + id); + return; + } // If the map has not rendered, stash the proposed `leafletProxy()` calls + // in `pendingRenderData.calls` to be run on display via `doRenderValue()`. + // This is necessary if the map has not been rendered. + // If new pendingRenderData is set via a new `leaflet()`, the previous calls will be discarded. + + + if (!map.leafletr.hasRendered) { + map.leafletr.pendingRenderData.calls = map.leafletr.pendingRenderData.calls.concat(data.calls); + return; + } + + for (var i = 0; i < data.calls.length; i++) { + var call = data.calls[i]; + var args = call.args; + + for (var _i = 0; _i < call.evals.length; _i++) { + window.HTMLWidgets.evaluateStringMember(args, call.evals[_i]); + } + + if (call.dependencies) { + _shiny2["default"].renderDependencies(call.dependencies); + } + + if (methods[call.method]) methods[call.method].apply(map, args);else (0, _util.log)("Unknown method " + call.method); + } + }); +} + + +},{"./cluster-layer-store":1,"./control-store":2,"./crs_utils":3,"./dataframe":4,"./fixup-default-icon":5,"./fixup-default-tooltip":6,"./fixup-url-protocol":7,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./layer-manager":14,"./methods":15,"./util":17}],14:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = undefined; + +var _jquery = require("./global/jquery"); + +var _jquery2 = _interopRequireDefault(_jquery); + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +var _util = require("./util"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var LayerManager = /*#__PURE__*/function () { + function LayerManager(map) { + _classCallCheck(this, LayerManager); + + this._map = map; // BEGIN layer indices + // {: {: layer}} + + this._byGroup = {}; // {: {: layer}} + + this._byCategory = {}; // {: layer} + + this._byLayerId = {}; // {: { + // "group": , + // "layerId": , + // "category": , + // "container": + // } + // } + + this._byStamp = {}; // {: {: [, , ...], ...}} + + this._byCrosstalkGroup = {}; // END layer indices + // {: L.layerGroup} + + this._categoryContainers = {}; // {: L.layerGroup} + + this._groupContainers = {}; + } + + _createClass(LayerManager, [{ + key: "addLayer", + value: function addLayer(layer, category, layerId, group, ctGroup, ctKey) { + var _this = this; + + // Was a group provided? + var hasId = typeof layerId === "string"; + var grouped = typeof group === "string"; + var stamp = _leaflet2["default"].Util.stamp(layer) + ""; // This will be the default layer group to add the layer to. + // We may overwrite this let before using it (i.e. if a group is assigned). + // This one liner creates the _categoryContainers[category] entry if it + // doesn't already exist. + + var container = this._categoryContainers[category] = this._categoryContainers[category] || _leaflet2["default"].layerGroup().addTo(this._map); + + var oldLayer = null; + + if (hasId) { + // First, remove any layer with the same category and layerId + var prefixedLayerId = this._layerIdKey(category, layerId); + + oldLayer = this._byLayerId[prefixedLayerId]; + + if (oldLayer) { + this._removeLayer(oldLayer); + } // Update layerId index + + + this._byLayerId[prefixedLayerId] = layer; + } // Update group index + + + if (grouped) { + this._byGroup[group] = this._byGroup[group] || {}; + this._byGroup[group][stamp] = layer; // Since a group is assigned, don't add the layer to the category's layer + // group; instead, use the group's layer group. + // This one liner creates the _groupContainers[group] entry if it doesn't + // already exist. + + container = this.getLayerGroup(group, true); + } // Update category index + + + this._byCategory[category] = this._byCategory[category] || {}; + this._byCategory[category][stamp] = layer; // Update stamp index + + var layerInfo = this._byStamp[stamp] = { + layer: layer, + group: group, + ctGroup: ctGroup, + ctKey: ctKey, + layerId: layerId, + category: category, + container: container, + hidden: false + }; // Update crosstalk group index + + if (ctGroup) { + if (layer.setStyle) { + // Need to save this info so we know what to set opacity to later + layer.options.origOpacity = typeof layer.options.opacity !== "undefined" ? layer.options.opacity : 0.5; + layer.options.origFillOpacity = typeof layer.options.fillOpacity !== "undefined" ? layer.options.fillOpacity : 0.2; + } + + var ctg = this._byCrosstalkGroup[ctGroup]; + + if (!ctg) { + ctg = this._byCrosstalkGroup[ctGroup] = {}; + var crosstalk = global.crosstalk; + + var handleFilter = function handleFilter(e) { + if (!e.value) { + var groupKeys = Object.keys(ctg); + + for (var i = 0; i < groupKeys.length; i++) { + var key = groupKeys[i]; + var _layerInfo = _this._byStamp[ctg[key]]; + + _this._setVisibility(_layerInfo, true); + } + } else { + var selectedKeys = {}; + + for (var _i = 0; _i < e.value.length; _i++) { + selectedKeys[e.value[_i]] = true; + } + + var _groupKeys = Object.keys(ctg); + + for (var _i2 = 0; _i2 < _groupKeys.length; _i2++) { + var _key = _groupKeys[_i2]; + var _layerInfo2 = _this._byStamp[ctg[_key]]; + + _this._setVisibility(_layerInfo2, selectedKeys[_groupKeys[_i2]]); + } + } + }; + + var filterHandle = new crosstalk.FilterHandle(ctGroup); + filterHandle.on("change", handleFilter); + + var handleSelection = function handleSelection(e) { + if (!e.value || !e.value.length) { + var groupKeys = Object.keys(ctg); + + for (var i = 0; i < groupKeys.length; i++) { + var key = groupKeys[i]; + var _layerInfo3 = _this._byStamp[ctg[key]]; + + _this._setOpacity(_layerInfo3, 1.0); + } + } else { + var selectedKeys = {}; + + for (var _i3 = 0; _i3 < e.value.length; _i3++) { + selectedKeys[e.value[_i3]] = true; + } + + var _groupKeys2 = Object.keys(ctg); + + for (var _i4 = 0; _i4 < _groupKeys2.length; _i4++) { + var _key2 = _groupKeys2[_i4]; + var _layerInfo4 = _this._byStamp[ctg[_key2]]; + + _this._setOpacity(_layerInfo4, selectedKeys[_groupKeys2[_i4]] ? 1.0 : 0.2); + } + } + }; + + var selHandle = new crosstalk.SelectionHandle(ctGroup); + selHandle.on("change", handleSelection); + setTimeout(function () { + handleFilter({ + value: filterHandle.filteredKeys + }); + handleSelection({ + value: selHandle.value + }); + }, 100); + } + + if (!ctg[ctKey]) ctg[ctKey] = []; + ctg[ctKey].push(stamp); + } // Add to container + + + if (!layerInfo.hidden) container.addLayer(layer); + return oldLayer; + } + }, { + key: "brush", + value: function brush(bounds, extraInfo) { + var _this2 = this; + + /* eslint-disable no-console */ + // For each Crosstalk group... + Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { + var ctg = _this2._byCrosstalkGroup[ctGroupName]; + var selection = []; // ...iterate over each Crosstalk key (each of which may have multiple + // layers)... + + Object.keys(ctg).forEach(function (ctKey) { + // ...and for each layer... + ctg[ctKey].forEach(function (stamp) { + var layerInfo = _this2._byStamp[stamp]; // ...if it's something with a point... + + if (layerInfo.layer.getLatLng) { + // ... and it's inside the selection bounds... + // TODO: Use pixel containment, not lat/lng containment + if (bounds.contains(layerInfo.layer.getLatLng())) { + // ...add the key to the selection. + selection.push(ctKey); + } + } + }); + }); + new global.crosstalk.SelectionHandle(ctGroupName).set(selection, extraInfo); + }); + } + }, { + key: "unbrush", + value: function unbrush(extraInfo) { + Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { + new global.crosstalk.SelectionHandle(ctGroupName).clear(extraInfo); + }); + } + }, { + key: "_setVisibility", + value: function _setVisibility(layerInfo, visible) { + if (layerInfo.hidden ^ visible) { + return; + } else if (visible) { + layerInfo.container.addLayer(layerInfo.layer); + layerInfo.hidden = false; + } else { + layerInfo.container.removeLayer(layerInfo.layer); + layerInfo.hidden = true; + } + } + }, { + key: "_setOpacity", + value: function _setOpacity(layerInfo, opacity) { + if (layerInfo.layer.setOpacity) { + layerInfo.layer.setOpacity(opacity); + } else if (layerInfo.layer.setStyle) { + layerInfo.layer.setStyle({ + opacity: opacity * layerInfo.layer.options.origOpacity, + fillOpacity: opacity * layerInfo.layer.options.origFillOpacity + }); + } + } + }, { + key: "getLayer", + value: function getLayer(category, layerId) { + return this._byLayerId[this._layerIdKey(category, layerId)]; + } + }, { + key: "removeLayer", + value: function removeLayer(category, layerIds) { + var _this3 = this; + + // Find layer info + _jquery2["default"].each((0, _util.asArray)(layerIds), function (i, layerId) { + var layer = _this3._byLayerId[_this3._layerIdKey(category, layerId)]; + + if (layer) { + _this3._removeLayer(layer); + } + }); + } + }, { + key: "clearLayers", + value: function clearLayers(category) { + var _this4 = this; + + // Find all layers in _byCategory[category] + var catTable = this._byCategory[category]; + + if (!catTable) { + return false; + } // Remove all layers. Make copy of keys to avoid mutating the collection + // behind the iterator you're accessing. + + + var stamps = []; + + _jquery2["default"].each(catTable, function (k, v) { + stamps.push(k); + }); + + _jquery2["default"].each(stamps, function (i, stamp) { + _this4._removeLayer(stamp); + }); + } + }, { + key: "getLayerGroup", + value: function getLayerGroup(group, ensureExists) { + var g = this._groupContainers[group]; + + if (ensureExists && !g) { + this._byGroup[group] = this._byGroup[group] || {}; + g = this._groupContainers[group] = _leaflet2["default"].featureGroup(); + g.groupname = group; + g.addTo(this._map); + } + + return g; + } + }, { + key: "getGroupNameFromLayerGroup", + value: function getGroupNameFromLayerGroup(layerGroup) { + return layerGroup.groupname; + } + }, { + key: "getVisibleGroups", + value: function getVisibleGroups() { + var _this5 = this; + + var result = []; + + _jquery2["default"].each(this._groupContainers, function (k, v) { + if (_this5._map.hasLayer(v)) { + result.push(k); + } + }); + + return result; + } + }, { + key: "getAllGroupNames", + value: function getAllGroupNames() { + var result = []; + + _jquery2["default"].each(this._groupContainers, function (k, v) { + result.push(k); + }); + + return result; + } + }, { + key: "clearGroup", + value: function clearGroup(group) { + var _this6 = this; + + // Find all layers in _byGroup[group] + var groupTable = this._byGroup[group]; + + if (!groupTable) { + return false; + } // Remove all layers. Make copy of keys to avoid mutating the collection + // behind the iterator you're accessing. + + + var stamps = []; + + _jquery2["default"].each(groupTable, function (k, v) { + stamps.push(k); + }); + + _jquery2["default"].each(stamps, function (i, stamp) { + _this6._removeLayer(stamp); + }); + } + }, { + key: "clear", + value: function clear() { + function clearLayerGroup(key, layerGroup) { + layerGroup.clearLayers(); + } // Clear all indices and layerGroups + + + this._byGroup = {}; + this._byCategory = {}; + this._byLayerId = {}; + this._byStamp = {}; + this._byCrosstalkGroup = {}; + + _jquery2["default"].each(this._categoryContainers, clearLayerGroup); + + this._categoryContainers = {}; + + _jquery2["default"].each(this._groupContainers, clearLayerGroup); + + this._groupContainers = {}; + } + }, { + key: "_removeLayer", + value: function _removeLayer(layer) { + var stamp; + + if (typeof layer === "string") { + stamp = layer; + } else { + stamp = _leaflet2["default"].Util.stamp(layer); + } + + var layerInfo = this._byStamp[stamp]; + + if (!layerInfo) { + return false; + } + + layerInfo.container.removeLayer(stamp); + + if (typeof layerInfo.group === "string") { + delete this._byGroup[layerInfo.group][stamp]; + } + + if (typeof layerInfo.layerId === "string") { + delete this._byLayerId[this._layerIdKey(layerInfo.category, layerInfo.layerId)]; + } + + delete this._byCategory[layerInfo.category][stamp]; + delete this._byStamp[stamp]; + + if (layerInfo.ctGroup) { + var ctGroup = this._byCrosstalkGroup[layerInfo.ctGroup]; + var layersForKey = ctGroup[layerInfo.ctKey]; + var idx = layersForKey ? layersForKey.indexOf(stamp) : -1; + + if (idx >= 0) { + if (layersForKey.length === 1) { + delete ctGroup[layerInfo.ctKey]; + } else { + layersForKey.splice(idx, 1); + } + } + } + } + }, { + key: "_layerIdKey", + value: function _layerIdKey(category, layerId) { + return category + "\n" + layerId; + } + }]); + + return LayerManager; +}(); + +exports["default"] = LayerManager; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./global/jquery":9,"./global/leaflet":10,"./util":17}],15:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _jquery = require("./global/jquery"); + +var _jquery2 = _interopRequireDefault(_jquery); + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +var _shiny = require("./global/shiny"); + +var _shiny2 = _interopRequireDefault(_shiny); + +var _htmlwidgets = require("./global/htmlwidgets"); + +var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); + +var _util = require("./util"); + +var _crs_utils = require("./crs_utils"); + +var _dataframe = require("./dataframe"); + +var _dataframe2 = _interopRequireDefault(_dataframe); + +var _clusterLayerStore = require("./cluster-layer-store"); + +var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); + +var _mipmapper = require("./mipmapper"); + +var _mipmapper2 = _interopRequireDefault(_mipmapper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var methods = {}; +exports["default"] = methods; + +function mouseHandler(mapId, layerId, group, eventName, extraInfo) { + return function (e) { + if (!_htmlwidgets2["default"].shinyMode) return; + var latLng = e.target.getLatLng ? e.target.getLatLng() : e.latlng; + + if (latLng) { + // retrieve only lat, lon values to remove prototype + // and extra parameters added by 3rd party modules + // these objects are for json serialization, not javascript + var latLngVal = _leaflet2["default"].latLng(latLng); // make sure it has consistent shape + + + latLng = { + lat: latLngVal.lat, + lng: latLngVal.lng + }; + } + + var eventInfo = _jquery2["default"].extend({ + id: layerId, + ".nonce": Math.random() // force reactivity + + }, group !== null ? { + group: group + } : null, latLng, extraInfo); + + _shiny2["default"].onInputChange(mapId + "_" + eventName, eventInfo); + }; +} + +methods.mouseHandler = mouseHandler; + +methods.clearGroup = function (group) { + var _this = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, v) { + _this.layerManager.clearGroup(v); + }); +}; + +methods.setView = function (center, zoom, options) { + this.setView(center, zoom, options); +}; + +methods.fitBounds = function (lat1, lng1, lat2, lng2, options) { + this.fitBounds([[lat1, lng1], [lat2, lng2]], options); +}; + +methods.flyTo = function (center, zoom, options) { + this.flyTo(center, zoom, options); +}; + +methods.flyToBounds = function (lat1, lng1, lat2, lng2, options) { + this.flyToBounds([[lat1, lng1], [lat2, lng2]], options); +}; + +methods.setMaxBounds = function (lat1, lng1, lat2, lng2) { + this.setMaxBounds([[lat1, lng1], [lat2, lng2]]); +}; + +methods.addPopups = function (lat, lng, popup, layerId, group, options) { + var _this2 = this; + + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("popup", popup).col("layerId", layerId).col("group", group).cbind(options); + + var _loop = function _loop(i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { + (function () { + var popup = _leaflet2["default"].popup(df.get(i)).setLatLng([df.get(i, "lat"), df.get(i, "lng")]).setContent(df.get(i, "popup")); + + var thisId = df.get(i, "layerId"); + var thisGroup = df.get(i, "group"); + this.layerManager.addLayer(popup, "popup", thisId, thisGroup); + }).call(_this2); + } + }; + + for (var i = 0; i < df.nrow(); i++) { + _loop(i); + } +}; + +methods.removePopup = function (layerId) { + this.layerManager.removeLayer("popup", layerId); +}; + +methods.clearPopups = function () { + this.layerManager.clearLayers("popup"); +}; + +methods.addTiles = function (urlTemplate, layerId, group, options) { + this.layerManager.addLayer(_leaflet2["default"].tileLayer(urlTemplate, options), "tile", layerId, group); +}; + +methods.removeTiles = function (layerId) { + this.layerManager.removeLayer("tile", layerId); +}; + +methods.clearTiles = function () { + this.layerManager.clearLayers("tile"); +}; + +methods.addWMSTiles = function (baseUrl, layerId, group, options) { + if (options && options.crs) { + options.crs = (0, _crs_utils.getCRS)(options.crs); + } + + this.layerManager.addLayer(_leaflet2["default"].tileLayer.wms(baseUrl, options), "tile", layerId, group); +}; // Given: +// {data: ["a", "b", "c"], index: [0, 1, 0, 2]} +// returns: +// ["a", "b", "a", "c"] + + +function unpackStrings(iconset) { + if (!iconset) { + return iconset; + } + + if (typeof iconset.index === "undefined") { + return iconset; + } + + iconset.data = (0, _util.asArray)(iconset.data); + iconset.index = (0, _util.asArray)(iconset.index); + return _jquery2["default"].map(iconset.index, function (e, i) { + return iconset.data[e]; + }); +} + +function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { + (function () { + var _this3 = this; + + var clusterGroup = this.layerManager.getLayer("cluster", clusterId), + cluster = clusterOptions !== null; + + if (cluster && !clusterGroup) { + clusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(clusterOptions); + + if (clusterOptions.freezeAtZoom) { + var freezeAtZoom = clusterOptions.freezeAtZoom; + delete clusterOptions.freezeAtZoom; + clusterGroup.freezeAtZoom(freezeAtZoom); + } + + clusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](clusterGroup); + } + + var extraInfo = cluster ? { + clusterId: clusterId + } : {}; + + var _loop2 = function _loop2(i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { + (function () { + var marker = markerFunc(df, i); + var thisId = df.get(i, "layerId"); + var thisGroup = cluster ? null : df.get(i, "group"); + + if (cluster) { + clusterGroup.clusterLayerStore.add(marker, thisId); + } else { + this.layerManager.addLayer(marker, "marker", thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); + } + + var popup = df.get(i, "popup"); + var popupOptions = df.get(i, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); + } + } + + var label = df.get(i, "label"); + var labelOptions = df.get(i, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); + } + } else { + marker.bindTooltip(label); + } + } + + marker.on("click", mouseHandler(this.id, thisId, thisGroup, "marker_click", extraInfo), this); + marker.on("mouseover", mouseHandler(this.id, thisId, thisGroup, "marker_mouseover", extraInfo), this); + marker.on("mouseout", mouseHandler(this.id, thisId, thisGroup, "marker_mouseout", extraInfo), this); + marker.on("dragend", mouseHandler(this.id, thisId, thisGroup, "marker_dragend", extraInfo), this); + }).call(_this3); + } + }; + + for (var i = 0; i < df.nrow(); i++) { + _loop2(i); + } + + if (cluster) { + this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); + } + }).call(map); +} + +methods.addGenericMarkers = addMarkers; + +methods.addMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { + var icondf; + var getIcon; + + if (icon) { + // Unpack icons + icon.iconUrl = unpackStrings(icon.iconUrl); + icon.iconRetinaUrl = unpackStrings(icon.iconRetinaUrl); + icon.shadowUrl = unpackStrings(icon.shadowUrl); + icon.shadowRetinaUrl = unpackStrings(icon.shadowRetinaUrl); // This cbinds the icon URLs and any other icon options; they're all + // present on the icon object. + + icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. + + getIcon = function getIcon(i) { + var opts = icondf.get(i); + + if (!opts.iconUrl) { + return new _leaflet2["default"].Icon.Default(); + } // Composite options (like points or sizes) are passed from R with each + // individual component as its own option. We need to combine them now + // into their composite form. + + + if (opts.iconWidth) { + opts.iconSize = [opts.iconWidth, opts.iconHeight]; + } + + if (opts.shadowWidth) { + opts.shadowSize = [opts.shadowWidth, opts.shadowHeight]; + } + + if (opts.iconAnchorX) { + opts.iconAnchor = [opts.iconAnchorX, opts.iconAnchorY]; + } + + if (opts.shadowAnchorX) { + opts.shadowAnchor = [opts.shadowAnchorX, opts.shadowAnchorY]; + } + + if (opts.popupAnchorX) { + opts.popupAnchor = [opts.popupAnchorX, opts.popupAnchorY]; + } + + return new _leaflet2["default"].Icon(opts); + }; + } + + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); + if (icon) icondf.effectiveLength = df.nrow(); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + var options = df.get(i); + if (icon) options.icon = getIcon(i); + return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }); + } +}; + +methods.addAwesomeMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { + var icondf; + var getIcon; + + if (icon) { + // This cbinds the icon URLs and any other icon options; they're all + // present on the icon object. + icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. + + getIcon = function getIcon(i) { + var opts = icondf.get(i); + + if (!opts) { + return new _leaflet2["default"].AwesomeMarkers.icon(); + } + + if (opts.squareMarker) { + opts.className = "awesome-marker awesome-marker-square"; + } + + return new _leaflet2["default"].AwesomeMarkers.icon(opts); + }; + } + + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); + if (icon) icondf.effectiveLength = df.nrow(); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + var options = df.get(i); + if (icon) options.icon = getIcon(i); + return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }); + } +}; + +function addLayers(map, category, df, layerFunc) { + var _loop3 = function _loop3(i) { + (function () { + var layer = layerFunc(df, i); + + if (!_jquery2["default"].isEmptyObject(layer)) { + var thisId = df.get(i, "layerId"); + var thisGroup = df.get(i, "group"); + this.layerManager.addLayer(layer, category, thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); + + if (layer.bindPopup) { + var popup = df.get(i, "popup"); + var popupOptions = df.get(i, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + layer.bindPopup(popup, popupOptions); + } else { + layer.bindPopup(popup); + } + } + } + + if (layer.bindTooltip) { + var label = df.get(i, "label"); + var labelOptions = df.get(i, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + layer.bindTooltip(label, labelOptions); + } else { + layer.bindTooltip(label); + } + } + } + + layer.on("click", mouseHandler(this.id, thisId, thisGroup, category + "_click"), this); + layer.on("mouseover", mouseHandler(this.id, thisId, thisGroup, category + "_mouseover"), this); + layer.on("mouseout", mouseHandler(this.id, thisId, thisGroup, category + "_mouseout"), this); + var highlightStyle = df.get(i, "highlightOptions"); + + if (!_jquery2["default"].isEmptyObject(highlightStyle)) { + var defaultStyle = {}; + + _jquery2["default"].each(highlightStyle, function (k, v) { + if (k != "bringToFront" && k != "sendToBack") { + if (df.get(i, k)) { + defaultStyle[k] = df.get(i, k); + } + } + }); + + layer.on("mouseover", function (e) { + this.setStyle(highlightStyle); + + if (highlightStyle.bringToFront) { + this.bringToFront(); + } + }); + layer.on("mouseout", function (e) { + this.setStyle(defaultStyle); + + if (highlightStyle.sendToBack) { + this.bringToBack(); + } + }); + } + } + }).call(map); + }; + + for (var i = 0; i < df.nrow(); i++) { + _loop3(i); + } +} + +methods.addGenericLayers = addLayers; + +methods.addCircles = function (lat, lng, radius, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions, crosstalkOptions) { + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options).cbind(crosstalkOptions || {}); + addLayers(this, "shape", df, function (df, i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng")) && _jquery2["default"].isNumeric(df.get(i, "radius"))) { + return _leaflet2["default"].circle([df.get(i, "lat"), df.get(i, "lng")], df.get(i, "radius"), df.get(i)); + } else { + return null; + } + }); + } +}; + +methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, clusterOptions, clusterId, popup, popupOptions, label, labelOptions, crosstalkOptions) { + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(crosstalkOptions || {}).cbind(options); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + return _leaflet2["default"].circleMarker([df.get(i, "lat"), df.get(i, "lng")], df.get(i)); + }); + } +}; +/* + * @param lat Array of arrays of latitude coordinates for polylines + * @param lng Array of arrays of longitude coordinates for polylines + */ + + +methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + if (polygons.length > 0) { + var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + var shapes = df.get(i, "shapes"); + shapes = shapes.map(function (shape) { + return _htmlwidgets2["default"].dataframeToD3(shape[0]); + }); + + if (shapes.length > 1) { + return _leaflet2["default"].polyline(shapes, df.get(i)); + } else { + return _leaflet2["default"].polyline(shapes[0], df.get(i)); + } + }); + } +}; + +methods.removeMarker = function (layerId) { + this.layerManager.removeLayer("marker", layerId); +}; + +methods.clearMarkers = function () { + this.layerManager.clearLayers("marker"); +}; + +methods.removeMarkerCluster = function (layerId) { + this.layerManager.removeLayer("cluster", layerId); +}; + +methods.removeMarkerFromCluster = function (layerId, clusterId) { + var cluster = this.layerManager.getLayer("cluster", clusterId); + if (!cluster) return; + cluster.clusterLayerStore.remove(layerId); +}; + +methods.clearMarkerClusters = function () { + this.layerManager.clearLayers("cluster"); +}; + +methods.removeShape = function (layerId) { + this.layerManager.removeLayer("shape", layerId); +}; + +methods.clearShapes = function () { + this.layerManager.clearLayers("shape"); +}; + +methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + var df = new _dataframe2["default"]().col("lat1", lat1).col("lng1", lng1).col("lat2", lat2).col("lng2", lng2).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat1")) && _jquery2["default"].isNumeric(df.get(i, "lng1")) && _jquery2["default"].isNumeric(df.get(i, "lat2")) && _jquery2["default"].isNumeric(df.get(i, "lng2"))) { + return _leaflet2["default"].rectangle([[df.get(i, "lat1"), df.get(i, "lng1")], [df.get(i, "lat2"), df.get(i, "lng2")]], df.get(i)); + } else { + return null; + } + }); +}; +/* + * @param lat Array of arrays of latitude coordinates for polygons + * @param lng Array of arrays of longitude coordinates for polygons + */ + + +methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + if (polygons.length > 0) { + var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + // This code used to use L.multiPolygon, but that caused + // double-click on a multipolygon to fail to zoom in on the + // map. Surprisingly, putting all the rings in a single + // polygon seems to still work; complicated multipolygons + // are still rendered correctly. + var shapes = df.get(i, "shapes").map(function (polygon) { + return polygon.map(_htmlwidgets2["default"].dataframeToD3); + }).reduce(function (acc, val) { + return acc.concat(val); + }, []); + return _leaflet2["default"].polygon(shapes, df.get(i)); + }); + } +}; + +methods.addGeoJSON = function (data, layerId, group, style) { + // This time, self is actually needed because the callbacks below need + // to access both the inner and outer senses of "this" + var self = this; + + if (typeof data === "string") { + data = JSON.parse(data); + } + + var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); + + var gjlayer = _leaflet2["default"].geoJson(data, { + style: function style(feature) { + if (feature.style || feature.properties.style) { + return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); + } else { + return globalStyle; + } + }, + onEachFeature: function onEachFeature(feature, layer) { + var extraInfo = { + featureId: feature.id, + properties: feature.properties + }; + var popup = feature.properties ? feature.properties.popup : null; + if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); + layer.on("click", mouseHandler(self.id, layerId, group, "geojson_click", extraInfo), this); + layer.on("mouseover", mouseHandler(self.id, layerId, group, "geojson_mouseover", extraInfo), this); + layer.on("mouseout", mouseHandler(self.id, layerId, group, "geojson_mouseout", extraInfo), this); + } + }); + + this.layerManager.addLayer(gjlayer, "geojson", layerId, group); +}; + +methods.removeGeoJSON = function (layerId) { + this.layerManager.removeLayer("geojson", layerId); +}; + +methods.clearGeoJSON = function () { + this.layerManager.clearLayers("geojson"); +}; + +methods.addTopoJSON = function (data, layerId, group, style) { + // This time, self is actually needed because the callbacks below need + // to access both the inner and outer senses of "this" + var self = this; + + if (typeof data === "string") { + data = JSON.parse(data); + } + + var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); + + var gjlayer = _leaflet2["default"].geoJson(null, { + style: function style(feature) { + if (feature.style || feature.properties.style) { + return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); + } else { + return globalStyle; + } + }, + onEachFeature: function onEachFeature(feature, layer) { + var extraInfo = { + featureId: feature.id, + properties: feature.properties + }; + var popup = feature.properties.popup; + if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); + layer.on("click", mouseHandler(self.id, layerId, group, "topojson_click", extraInfo), this); + layer.on("mouseover", mouseHandler(self.id, layerId, group, "topojson_mouseover", extraInfo), this); + layer.on("mouseout", mouseHandler(self.id, layerId, group, "topojson_mouseout", extraInfo), this); + } + }); + + global.omnivore.topojson.parse(data, null, gjlayer); + this.layerManager.addLayer(gjlayer, "topojson", layerId, group); +}; + +methods.removeTopoJSON = function (layerId) { + this.layerManager.removeLayer("topojson", layerId); +}; + +methods.clearTopoJSON = function () { + this.layerManager.clearLayers("topojson"); +}; + +methods.addControl = function (html, position, layerId, classes) { + function onAdd(map) { + var div = _leaflet2["default"].DomUtil.create("div", classes); + + if (typeof layerId !== "undefined" && layerId !== null) { + div.setAttribute("id", layerId); + } + + this._div = div; // It's possible for window.Shiny to be true but Shiny.initializeInputs to + // not be, when a static leaflet widget is included as part of the shiny + // UI directly (not through leafletOutput or uiOutput). In this case we + // don't do the normal Shiny stuff as that will all happen when Shiny + // itself loads and binds the entire doc. + + if (window.Shiny && _shiny2["default"].initializeInputs) { + _shiny2["default"].renderHtml(html, this._div); + + _shiny2["default"].initializeInputs(this._div); + + _shiny2["default"].bindAll(this._div); + } else { + this._div.innerHTML = html; + } + + return this._div; + } + + function onRemove(map) { + if (window.Shiny && _shiny2["default"].unbindAll) { + _shiny2["default"].unbindAll(this._div); + } + } + + var Control = _leaflet2["default"].Control.extend({ + options: { + position: position + }, + onAdd: onAdd, + onRemove: onRemove + }); + + this.controls.add(new Control(), layerId, html); +}; + +methods.addCustomControl = function (control, layerId) { + this.controls.add(control, layerId); +}; + +methods.removeControl = function (layerId) { + this.controls.remove(layerId); +}; + +methods.getControl = function (layerId) { + this.controls.get(layerId); +}; + +methods.clearControls = function () { + this.controls.clear(); +}; + +methods.addLegend = function (options) { + var legend = _leaflet2["default"].control({ + position: options.position + }); + + var gradSpan; + + legend.onAdd = function (map) { + var div = _leaflet2["default"].DomUtil.create("div", options.className), + colors = options.colors, + labels = options.labels, + legendHTML = ""; + + if (options.type === "numeric") { + // # Formatting constants. + var singleBinHeight = 20; // The distance between tick marks, in px + + var vMargin = 8; // If 1st tick mark starts at top of gradient, how + // many extra px are needed for the top half of the + // 1st label? (ditto for last tick mark/label) + + var tickWidth = 4; // How wide should tick marks be, in px? + + var labelPadding = 6; // How much distance to reserve for tick mark? + // (Must be >= tickWidth) + // # Derived formatting parameters. + // What's the height of a single bin, in percentage (of gradient height)? + // It might not just be 1/(n-1), if the gradient extends past the tick + // marks (which can be the case for pretty cut points). + + var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); // Each bin is `singleBinHeight` high. How tall is the gradient? + + var totalHeight = 1 / singleBinPct * singleBinHeight + 1; // How far should the first tick be shifted down, relative to the top + // of the gradient? + + var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; + gradSpan = (0, _jquery2["default"])("").css({ + "background": "linear-gradient(" + colors + ")", + "opacity": options.opacity, + "height": totalHeight + "px", + "width": "18px", + "display": "block", + "margin-top": vMargin + "px" + }); + var leftDiv = (0, _jquery2["default"])("
    ").css("float", "left"), + rightDiv = (0, _jquery2["default"])("
    ").css("float", "left"); + leftDiv.append(gradSpan); + (0, _jquery2["default"])(div).append(leftDiv).append(rightDiv).append((0, _jquery2["default"])("
    ")); // Have to attach the div to the body at this early point, so that the + // svg text getComputedTextLength() actually works, below. + + document.body.appendChild(div); + var ns = "http://www.w3.org/2000/svg"; + var svg = document.createElementNS(ns, "svg"); + rightDiv.append(svg); + var g = document.createElementNS(ns, "g"); + (0, _jquery2["default"])(g).attr("transform", "translate(0, " + vMargin + ")"); + svg.appendChild(g); // max label width needed to set width of svg, and right-justify text + + var maxLblWidth = 0; // Create tick marks and labels + + _jquery2["default"].each(labels, function (i, label) { + var y = tickOffset + i * singleBinHeight + 0.5; + var thisLabel = document.createElementNS(ns, "text"); + (0, _jquery2["default"])(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); + g.appendChild(thisLabel); + maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); + var thisTick = document.createElementNS(ns, "line"); + (0, _jquery2["default"])(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); + g.appendChild(thisTick); + }); // Now that we know the max label width, we can right-justify + + + (0, _jquery2["default"])(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); // Final size for + + (0, _jquery2["default"])(svg).css({ + width: maxLblWidth + labelPadding + "px", + height: totalHeight + vMargin * 2 + "px" + }); + + if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { + (0, _jquery2["default"])(div).append("
    " + options.na_label + "
    "); + } + } else { + if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { + colors.push(options.na_color); + labels.push(options.na_label); + } + + for (var i = 0; i < colors.length; i++) { + legendHTML += " " + labels[i] + "
    "; + } + + div.innerHTML = legendHTML; + } + + if (options.title) (0, _jquery2["default"])(div).prepend("
    " + options.title + "
    "); + return div; + }; + + if (options.group) { + // Auto generate a layerID if not provided + if (!options.layerId) { + options.layerId = _leaflet2["default"].Util.stamp(legend); + } + + var map = this; + map.on("overlayadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); + } + }); + map.on("overlayremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); + } + }); + map.on("groupadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); + } + }); + map.on("groupremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); + } + }); + } + + this.controls.add(legend, options.layerId); +}; + +methods.addLayersControl = function (baseGroups, overlayGroups, options) { + var _this4 = this; + + // Only allow one layers control at a time + methods.removeLayersControl.call(this); + var firstLayer = true; + var base = {}; + + _jquery2["default"].each((0, _util.asArray)(baseGroups), function (i, g) { + var layer = _this4.layerManager.getLayerGroup(g, true); + + if (layer) { + base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one + + if (_this4.hasLayer(layer)) { + if (firstLayer) { + firstLayer = false; + } else { + _this4.removeLayer(layer); + } + } + } + }); + + var overlay = {}; + + _jquery2["default"].each((0, _util.asArray)(overlayGroups), function (i, g) { + var layer = _this4.layerManager.getLayerGroup(g, true); + + if (layer) { + overlay[g] = layer; + } + }); + + this.currentLayersControl = _leaflet2["default"].control.layers(base, overlay, options); + this.addControl(this.currentLayersControl); +}; + +methods.removeLayersControl = function () { + if (this.currentLayersControl) { + this.removeControl(this.currentLayersControl); + this.currentLayersControl = null; + } +}; + +methods.addScaleBar = function (options) { + // Only allow one scale bar at a time + methods.removeScaleBar.call(this); + + var scaleBar = _leaflet2["default"].control.scale(options).addTo(this); + + this.currentScaleBar = scaleBar; +}; + +methods.removeScaleBar = function () { + if (this.currentScaleBar) { + this.currentScaleBar.remove(); + this.currentScaleBar = null; + } +}; + +methods.hideGroup = function (group) { + var _this5 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this5.layerManager.getLayerGroup(g, true); + + if (layer) { + _this5.removeLayer(layer); + } + }); +}; + +methods.showGroup = function (group) { + var _this6 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this6.layerManager.getLayerGroup(g, true); + + if (layer) { + _this6.addLayer(layer); + } + }); +}; + +function setupShowHideGroupsOnZoom(map) { + if (map.leafletr._hasInitializedShowHideGroups) { + return; + } + + map.leafletr._hasInitializedShowHideGroups = true; + + function setVisibility(layer, visible, group) { + if (visible !== map.hasLayer(layer)) { + if (visible) { + map.addLayer(layer); + map.fire("groupadd", { + "name": group, + "layer": layer + }); + } else { + map.removeLayer(layer); + map.fire("groupremove", { + "name": group, + "layer": layer + }); + } + } + } + + function showHideGroupsOnZoom() { + if (!map.layerManager) return; + var zoom = map.getZoom(); + map.layerManager.getAllGroupNames().forEach(function (group) { + var layer = map.layerManager.getLayerGroup(group, false); + + if (layer && typeof layer.zoomLevels !== "undefined") { + setVisibility(layer, layer.zoomLevels === true || layer.zoomLevels.indexOf(zoom) >= 0, group); + } + }); + } + + map.showHideGroupsOnZoom = showHideGroupsOnZoom; + map.on("zoomend", showHideGroupsOnZoom); +} + +methods.setGroupOptions = function (group, options) { + var _this7 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels + + + if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) { + layer.zoomLevels = (0, _util.asArray)(options.zoomLevels); + } + }); + + setupShowHideGroupsOnZoom(this); + this.showHideGroupsOnZoom(); +}; + +methods.addRasterImage = function (uri, bounds, layerId, group, options) { + // uri is a data URI containing an image. We want to paint this image as a + // layer at (top-left) bounds[0] to (bottom-right) bounds[1]. + // We can't simply use ImageOverlay, as it uses bilinear scaling which looks + // awful as you zoom in (and sometimes shifts positions or disappears). + // Instead, we'll use a TileLayer.Canvas to draw pieces of the image. + // First, some helper functions. + // degree2tile converts latitude, longitude, and zoom to x and y tile + // numbers. The tile numbers returned can be non-integral, as there's no + // reason to expect that the lat/lng inputs are exactly on the border of two + // tiles. + // + // We'll use this to convert the bounds we got from the server, into coords + // in tile-space at a given zoom level. Note that once we do the conversion, + // we don't to do any more trigonometry to convert between pixel coordinates + // and tile coordinates; the source image pixel coords, destination canvas + // pixel coords, and tile coords all can be scaled linearly. + function degree2tile(lat, lng, zoom) { + // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames + var latRad = lat * Math.PI / 180; + var n = Math.pow(2, zoom); + var x = (lng + 180) / 360 * n; + var y = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n; + return { + x: x, + y: y + }; + } // Given a range [from,to) and either one or two numbers, returns true if + // there is any overlap between [x,x1) and the range--or if x1 is omitted, + // then returns true if x is within [from,to). + + + function overlap(from, to, x, + /* optional */ + x1) { + if (arguments.length == 3) x1 = x; + return x < to && x1 >= from; + } + + function getCanvasSmoothingProperty(ctx) { + var candidates = ["imageSmoothingEnabled", "mozImageSmoothingEnabled", "webkitImageSmoothingEnabled", "msImageSmoothingEnabled"]; + + for (var i = 0; i < candidates.length; i++) { + if (typeof ctx[candidates[i]] !== "undefined") { + return candidates[i]; + } + } + + return null; + } // Our general strategy is to: + // 1. Load the data URI in an Image() object, so we can get its pixel + // dimensions and the underlying image data. (We could have done this + // by not encoding as PNG at all but just send an array of RGBA values + // from the server, but that would inflate the JSON too much.) + // 2. Create a hidden canvas that we use just to extract the image data + // from the Image (using Context2D.getImageData()). + // 3. Create a TileLayer.Canvas and add it to the map. + // We want to synchronously create and attach the TileLayer.Canvas (so an + // immediate call to clearRasters() will be respected, for example), but + // Image loads its data asynchronously. Fortunately we can resolve this + // by putting TileLayer.Canvas into async mode, which will let us create + // and attach the layer but have it wait until the image is loaded before + // it actually draws anything. + // These are the variables that we will populate once the image is loaded. + + + var imgData = null; // 1d row-major array, four [0-255] integers per pixel + + var imgDataMipMapper = null; + var w = null; // image width in pixels + + var h = null; // image height in pixels + // We'll use this array to store callbacks that need to be invoked once + // imgData, w, and h have been resolved. + + var imgDataCallbacks = []; // Consumers of imgData, w, and h can call this to be notified when data + // is available. + + function getImageData(callback) { + if (imgData != null) { + // Must not invoke the callback immediately; it's too confusing and + // fragile to have a function invoke the callback *either* immediately + // or in the future. Better to be consistent here. + setTimeout(function () { + callback(imgData, w, h, imgDataMipMapper); + }, 0); + } else { + imgDataCallbacks.push(callback); + } + } + + var img = new Image(); + + img.onload = function () { + // Save size + w = img.width; + h = img.height; // Create a dummy canvas to extract the image data + + var imgDataCanvas = document.createElement("canvas"); + imgDataCanvas.width = w; + imgDataCanvas.height = h; + imgDataCanvas.style.display = "none"; + document.body.appendChild(imgDataCanvas); + var imgDataCtx = imgDataCanvas.getContext("2d"); + imgDataCtx.drawImage(img, 0, 0); // Save the image data. + + imgData = imgDataCtx.getImageData(0, 0, w, h).data; + imgDataMipMapper = new _mipmapper2["default"](img); // Done with the canvas, remove it from the page so it can be gc'd. + + document.body.removeChild(imgDataCanvas); // Alert any getImageData callers who are waiting. + + for (var i = 0; i < imgDataCallbacks.length; i++) { + imgDataCallbacks[i](imgData, w, h, imgDataMipMapper); + } + + imgDataCallbacks = []; + }; + + img.src = uri; + + var canvasTiles = _leaflet2["default"].gridLayer(Object.assign({}, options, { + detectRetina: true, + async: true + })); // NOTE: The done() function MUST NOT be invoked until after the current + // tick; done() looks in Leaflet's tile cache for the current tile, and + // since it's still being constructed, it won't be found. + + + canvasTiles.createTile = function (tilePoint, done) { + var zoom = tilePoint.z; + + var canvas = _leaflet2["default"].DomUtil.create("canvas"); + + var error; // setup tile width and height according to the options + + var size = this.getTileSize(); + canvas.width = size.x; + canvas.height = size.y; + getImageData(function (imgData, w, h, mipmapper) { + try { + // The Context2D we'll being drawing onto. It's always 256x256. + var ctx = canvas.getContext("2d"); // Convert our image data's top-left and bottom-right locations into + // x/y tile coordinates. This is essentially doing a spherical mercator + // projection, then multiplying by 2^zoom. + + var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); + var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); // The size of the image in x/y tile coordinates. + + var extent = { + x: bottomRight.x - topLeft.x, + y: bottomRight.y - topLeft.y + }; // Short circuit if tile is totally disjoint from image. + + if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return; + if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; // The linear resolution of the tile we're drawing is always 256px per tile unit. + // If the linear resolution (in either direction) of the image is less than 256px + // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in + // scaling. + + var imgRes = { + x: w / extent.x, + y: h / extent.y + }; // We can do the actual drawing in one of three ways: + // - Call drawImage(). This is easy and fast, and results in smooth + // interpolation (bilinear?). This is what we want when we are + // reducing the image from its native size. + // - Call drawImage() with imageSmoothingEnabled=false. This is easy + // and fast and gives us nearest-neighbor interpolation, which is what + // we want when enlarging the image. However, it's unsupported on many + // browsers (including QtWebkit). + // - Do a manual nearest-neighbor interpolation. This is what we'll fall + // back to when enlarging, and imageSmoothingEnabled isn't supported. + // In theory it's slower, but still pretty fast on my machine, and the + // results look the same AFAICT. + // Is imageSmoothingEnabled supported? If so, we can let canvas do + // nearest-neighbor interpolation for us. + + var smoothingProperty = getCanvasSmoothingProperty(ctx); + + if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { + // Use built-in scaling + // Turn off anti-aliasing if necessary + if (smoothingProperty) { + ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; + } // Don't necessarily draw with the full-size image; if we're + // downscaling, use the mipmapper to get a pre-downscaled image + // (see comments on Mipmapper class for why this matters). + + + mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { + // It's possible that the image will go off the edge of the canvas-- + // that's OK, the canvas should clip appropriately. + ctx.drawImage(mip, // Convert abs tile coords to rel tile coords, then *256 to convert + // to rel pixel coords + (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, // Always draw the whole thing and let canvas clip; so we can just + // convert from size in tile coords straight to pixels + extent.x * 256, extent.y * 256); + }); + } else { + // Use manual nearest-neighbor interpolation + // Calculate the source image pixel coordinates that correspond with + // the top-left and bottom-right of this tile. (If the source image + // only partially overlaps the tile, we use max/min to limit the + // sourceStart/End to only reflect the overlapping portion.) + var sourceStart = { + x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), + y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) + }; + var sourceEnd = { + x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), + y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) + }; // The size, in dest pixels, that each source pixel should occupy. + // This might be greater or less than 1 (e.g. if x and y resolution + // are very different). + + var pixelSize = { + x: 256 / imgRes.x, + y: 256 / imgRes.y + }; // For each pixel in the source image that overlaps the tile... + + for (var row = sourceStart.y; row < sourceEnd.y; row++) { + for (var col = sourceStart.x; col < sourceEnd.x; col++) { + // ...extract the pixel data... + var i = (row * w + col) * 4; + var r = imgData[i]; + var g = imgData[i + 1]; + var b = imgData[i + 2]; + var a = imgData[i + 3]; + ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; // ...calculate the corresponding pixel coord in the dest image + // where it should be drawn... + + var pixelPos = { + x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, + y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 + }; // ...and draw a rectangle there. + + ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), // Looks crazy, but this is necessary to prevent rounding from + // causing overlap between this rect and its neighbors. The + // minuend is the location of the next pixel, while the + // subtrahend is the position of the current pixel (to turn an + // absolute coordinate to a width/height). Yes, I had to look + // up minuend and subtrahend. + Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); + } + } + } + } catch (e) { + error = e; + } finally { + done(error, canvas); + } + }); + return canvas; + }; + + this.layerManager.addLayer(canvasTiles, "image", layerId, group); +}; + +methods.removeImage = function (layerId) { + this.layerManager.removeLayer("image", layerId); +}; + +methods.clearImages = function () { + this.layerManager.clearLayers("image"); +}; + +methods.addMeasure = function (options) { + // if a measureControl already exists, then remove it and + // replace with a new one + methods.removeMeasure.call(this); + this.measureControl = _leaflet2["default"].control.measure(options); + this.addControl(this.measureControl); +}; + +methods.removeMeasure = function () { + if (this.measureControl) { + this.removeControl(this.measureControl); + this.measureControl = null; + } +}; + +methods.addSelect = function (ctGroup) { + var _this8 = this; + + methods.removeSelect.call(this); + this._selectButton = _leaflet2["default"].easyButton({ + states: [{ + stateName: "select-inactive", + icon: "ion-qr-scanner", + title: "Make a selection", + onClick: function onClick(btn, map) { + btn.state("select-active"); + _this8._locationFilter = new _leaflet2["default"].LocationFilter2(); + + if (ctGroup) { + var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); + selectionHandle.on("change", function (e) { + if (e.sender !== selectionHandle) { + if (_this8._locationFilter) { + _this8._locationFilter.disable(); + + btn.state("select-inactive"); + } + } + }); + + var handler = function handler(e) { + _this8.layerManager.brush(_this8._locationFilter.getBounds(), { + sender: selectionHandle + }); + }; + + _this8._locationFilter.on("enabled", handler); + + _this8._locationFilter.on("change", handler); + + _this8._locationFilter.on("disabled", function () { + selectionHandle.close(); + _this8._locationFilter = null; + }); + } + + _this8._locationFilter.addTo(map); + } + }, { + stateName: "select-active", + icon: "ion-close-round", + title: "Dismiss selection", + onClick: function onClick(btn, map) { + btn.state("select-inactive"); + + _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections + + + _this8.layerManager.unbrush(); + } + }] + }); + + this._selectButton.addTo(this); +}; + +methods.removeSelect = function () { + if (this._locationFilter) { + this._locationFilter.disable(); + } + + if (this._selectButton) { + this.removeControl(this._selectButton); + this._selectButton = null; + } +}; + +methods.createMapPane = function (name, zIndex) { + this.createPane(name); + this.getPane(name).style.zIndex = zIndex; +}; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./cluster-layer-store":1,"./crs_utils":3,"./dataframe":4,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./mipmapper":16,"./util":17}],16:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +// This class simulates a mipmap, which shrinks images by powers of two. This +// stepwise reduction results in "pixel-perfect downscaling" (where every +// pixel of the original image has some contribution to the downscaled image) +// as opposed to a single-step downscaling which will discard a lot of data +// (and with sparse images at small scales can give very surprising results). +var Mipmapper = /*#__PURE__*/function () { + function Mipmapper(img) { + _classCallCheck(this, Mipmapper); + + this._layers = [img]; + } // The various functions on this class take a callback function BUT MAY OR MAY + // NOT actually behave asynchronously. + + + _createClass(Mipmapper, [{ + key: "getBySize", + value: function getBySize(desiredWidth, desiredHeight, callback) { + var _this = this; + + var i = 0; + var lastImg = this._layers[0]; + + var testNext = function testNext() { + _this.getByIndex(i, function (img) { + // If current image is invalid (i.e. too small to be rendered) or + // it's smaller than what we wanted, return the last known good image. + if (!img || img.width < desiredWidth || img.height < desiredHeight) { + callback(lastImg); + return; + } else { + lastImg = img; + i++; + testNext(); + return; + } + }); + }; + + testNext(); + } + }, { + key: "getByIndex", + value: function getByIndex(i, callback) { + var _this2 = this; + + if (this._layers[i]) { + callback(this._layers[i]); + return; + } + + this.getByIndex(i - 1, function (prevImg) { + if (!prevImg) { + // prevImg could not be calculated (too small, possibly) + callback(null); + return; + } + + if (prevImg.width < 2 || prevImg.height < 2) { + // Can't reduce this image any further + callback(null); + return; + } // If reduce ever becomes truly asynchronous, we should stuff a promise or + // something into this._layers[i] before calling this.reduce(), to prevent + // redundant reduce operations from happening. + + + _this2.reduce(prevImg, function (reducedImg) { + _this2._layers[i] = reducedImg; + callback(reducedImg); + return; + }); + }); + } + }, { + key: "reduce", + value: function reduce(img, callback) { + var imgDataCanvas = document.createElement("canvas"); + imgDataCanvas.width = Math.ceil(img.width / 2); + imgDataCanvas.height = Math.ceil(img.height / 2); + imgDataCanvas.style.display = "none"; + document.body.appendChild(imgDataCanvas); + + try { + var imgDataCtx = imgDataCanvas.getContext("2d"); + imgDataCtx.drawImage(img, 0, 0, img.width / 2, img.height / 2); + callback(imgDataCanvas); + } finally { + document.body.removeChild(imgDataCanvas); + } + } + }]); + + return Mipmapper; +}(); + +exports["default"] = Mipmapper; + + +},{}],17:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.log = log; +exports.recycle = recycle; +exports.asArray = asArray; + +function log(message) { + /* eslint-disable no-console */ + if (console && console.log) console.log(message); + /* eslint-enable no-console */ +} + +function recycle(values, length, inPlace) { + if (length === 0 && !inPlace) return []; + + if (!(values instanceof Array)) { + if (inPlace) { + throw new Error("Can't do in-place recycling of a non-Array value"); + } + + values = [values]; + } + + if (typeof length === "undefined") length = values.length; + var dest = inPlace ? values : []; + var origLength = values.length; + + while (dest.length < length) { + dest.push(values[dest.length % origLength]); + } + + if (dest.length > length) { + dest.splice(length, dest.length - length); + } + + return dest; +} + +function asArray(value) { + if (value instanceof Array) return value;else return [value]; +} + + +},{}]},{},[13]); diff --git a/html_outputs/site_libs/leaflet-providers-plugin-2.2.2/leaflet-providers-plugin.js b/html_outputs/site_libs/leaflet-providers-plugin-2.2.2/leaflet-providers-plugin.js new file mode 100644 index 00000000..82cd6301 --- /dev/null +++ b/html_outputs/site_libs/leaflet-providers-plugin-2.2.2/leaflet-providers-plugin.js @@ -0,0 +1,3 @@ +LeafletWidget.methods.addProviderTiles = function(provider, layerId, group, options) { + this.layerManager.addLayer(L.tileLayer.provider(provider, options), "tile", layerId, group); +}; diff --git a/html_outputs/site_libs/quarto-contrib/glightbox/lightbox.css b/html_outputs/site_libs/quarto-contrib/glightbox/lightbox.css index 2c29fe1b..46432d99 100644 --- a/html_outputs/site_libs/quarto-contrib/glightbox/lightbox.css +++ b/html_outputs/site_libs/quarto-contrib/glightbox/lightbox.css @@ -5,6 +5,10 @@ body:not(.glightbox-mobile) div.gslide-description .gslide-desc { background-color: var(--quarto-body-bg); } +body:not(.glightbox-mobile) div.gslide-media { + background-color: var(--quarto-body-bg); +} + .goverlay { background: rgba(0, 0, 0, 0.7); } diff --git a/html_outputs/site_libs/quarto-html/quarto-syntax-highlighting.css b/html_outputs/site_libs/quarto-html/quarto-syntax-highlighting.css index d9fd98f0..b30ce576 100644 --- a/html_outputs/site_libs/quarto-html/quarto-syntax-highlighting.css +++ b/html_outputs/site_libs/quarto-html/quarto-syntax-highlighting.css @@ -85,6 +85,7 @@ code span.st { code span.cf { color: #003B4F; + font-weight: bold; font-style: inherit; } @@ -193,6 +194,7 @@ code span.dv { code span.kw { color: #003B4F; + font-weight: bold; font-style: inherit; } diff --git a/html_outputs/site_libs/quarto-html/quarto.js b/html_outputs/site_libs/quarto-html/quarto.js index 3ebd49cb..39e68699 100644 --- a/html_outputs/site_libs/quarto-html/quarto.js +++ b/html_outputs/site_libs/quarto-html/quarto.js @@ -94,7 +94,7 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { if (link.href.indexOf("#") !== -1) { const anchor = link.href.split("#")[1]; const heading = window.document.querySelector( - `[data-anchor-id=${anchor}]` + `[data-anchor-id="${anchor}"]` ); if (heading) { // Add the class @@ -134,8 +134,10 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { window.innerHeight + window.pageYOffset >= window.document.body.offsetHeight ) { + // This is the no-scroll case where last section should be the active one sectionIndex = 0; } else { + // This finds the last section visible on screen that should be made active sectionIndex = [...sections].reverse().findIndex((section) => { if (section) { return window.pageYOffset >= section.offsetTop - sectionMargin; @@ -317,6 +319,7 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { for (const child of el.children) { child.style.opacity = 0; child.style.overflow = "hidden"; + child.style.pointerEvents = "none"; } nexttick(() => { @@ -358,6 +361,7 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { const clone = child.cloneNode(true); clone.style.opacity = 1; + clone.style.pointerEvents = null; clone.style.display = null; toggleContents.append(clone); } @@ -432,6 +436,7 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { for (const child of el.children) { child.style.opacity = 1; child.style.overflow = null; + child.style.pointerEvents = null; } const placeholderEl = window.document.getElementById( @@ -739,6 +744,7 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { // Process the collapse state if this is an UL if (el.tagName === "UL") { if (tocOpenDepth === -1 && depth > 1) { + // toc-expand: false el.classList.add("collapse"); } else if ( depth <= tocOpenDepth || @@ -757,10 +763,9 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { }; // walk the TOC and expand / collapse any items that should be shown - if (tocEl) { - walk(tocEl, 0); updateActiveLink(); + walk(tocEl, 0); } // Throttle the scroll event and walk peridiocally @@ -779,6 +784,10 @@ window.document.addEventListener("DOMContentLoaded", function (_event) { window.addEventListener( "resize", throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } if (!isReaderMode()) { hideOverlappedSidebars(); } diff --git a/html_outputs/site_libs/quarto-nav/quarto-nav.js b/html_outputs/site_libs/quarto-nav/quarto-nav.js index ebfc262e..38cc4305 100644 --- a/html_outputs/site_libs/quarto-nav/quarto-nav.js +++ b/html_outputs/site_libs/quarto-nav/quarto-nav.js @@ -5,9 +5,45 @@ const headroomChanged = new CustomEvent("quarto-hrChanged", { composed: false, }); +const announceDismiss = () => { + const annEl = window.document.getElementById("quarto-announcement"); + if (annEl) { + annEl.remove(); + + const annId = annEl.getAttribute("data-announcement-id"); + window.localStorage.setItem(`quarto-announce-${annId}`, "true"); + } +}; + +const announceRegister = () => { + const annEl = window.document.getElementById("quarto-announcement"); + if (annEl) { + const annId = annEl.getAttribute("data-announcement-id"); + const isDismissed = + window.localStorage.getItem(`quarto-announce-${annId}`) || false; + if (isDismissed) { + announceDismiss(); + return; + } else { + annEl.classList.remove("hidden"); + } + + const actionEl = annEl.querySelector(".quarto-announcement-action"); + if (actionEl) { + actionEl.addEventListener("click", function (e) { + e.preventDefault(); + // Hide the bar immediately + announceDismiss(); + }); + } + } +}; + window.document.addEventListener("DOMContentLoaded", function () { let init = false; + announceRegister(); + // Manage the back to top button, if one is present. let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollDownBuffer = 5; @@ -237,6 +273,7 @@ window.document.addEventListener("DOMContentLoaded", function () { const links = window.document.querySelectorAll("a"); for (let i = 0; i < links.length; i++) { if (links[i].href) { + links[i].dataset.originalHref = links[i].href; links[i].href = links[i].href.replace(/\/index\.html/, "/"); } } diff --git a/html_outputs/site_libs/quarto-search/quarto-search.js b/html_outputs/site_libs/quarto-search/quarto-search.js index 5f723d72..d788a958 100644 --- a/html_outputs/site_libs/quarto-search/quarto-search.js +++ b/html_outputs/site_libs/quarto-search/quarto-search.js @@ -1275,7 +1275,11 @@ async function fuseSearch(query, fuse, fuseOptions) { // If we don't have a subfuse and the query is long enough, go ahead // and create a subfuse to use for subsequent queries - if (now - then > kFuseMaxWait && subSearchFuse === undefined) { + if ( + now - then > kFuseMaxWait && + subSearchFuse === undefined && + resultsRaw.length < fuseOptions.limit + ) { subSearchTerm = query; subSearchFuse = new window.Fuse([], kFuseIndexOptions); resultsRaw.forEach((rr) => { diff --git a/html_outputs/tr/new_pages/data_used.tr.html b/html_outputs/tr/new_pages/data_used.tr.html index e5172ced..52166599 100644 --- a/html_outputs/tr/new_pages/data_used.tr.html +++ b/html_outputs/tr/new_pages/data_used.tr.html @@ -707,7 +707,7 @@

    İndirme linkini kullanarak

    -

    Linke hızlıca erişebilmek için, bu linki sağ fare tuşunuzla tıklayın link ve “Farklı Kaydet” seçeneğini seçin.

    +

    Linke hızlıca erişebilmek için, bu linki sağ fare tuşunuzla tıklayın link ve “Farklı Kaydet” seçeneğini seçin.

    Mac kullanıyorsanız, Cmd’ye basılı tutarak farenizle tıklayın. Cep telefonu kullanıyorsanız, bağlantıyı basılı tutun ve “Bağlantıyı kaydet”i seçin. Kitap cihazınıza indirilecektir. Görseller olmadan HTML kodu içeren bir ekran görürseniz, yukarıdaki talimatları uyguladığınızdan emin olun veya ikinci seçeneği deneyin.

    @@ -1507,4 +1507,4 @@

    Shiny

    - + \ No newline at end of file diff --git a/html_outputs/vn/new_pages/data_used.vn.html b/html_outputs/vn/new_pages/data_used.vn.html index 9d92c433..7a2b061e 100644 --- a/html_outputs/vn/new_pages/data_used.vn.html +++ b/html_outputs/vn/new_pages/data_used.vn.html @@ -710,7 +710,7 @@

    Sử dụng link download

    -

    Để truy cập nhanh, nháy phải chuột vào link này và lựa chọn “Save link as”.

    +

    Để truy cập nhanh, nháy phải chuột vào link này và lựa chọn “Save link as”.

    Nếu trên máy Mac, hãy sử dụng Cmd + Nhấp chuột. Nếu trên điện thoại di động, hãy bấm và giữ liên kết và chọn “Save link”. Sổ tay sẽ tải xuống thiết bị của bạn. Nếu trên màn hình xuất hiện mã HTML gốc, hãy đảm bảo bạn đã làm đúng theo các hướng dẫn bên trên hoặc thử Phương án 2.

    diff --git a/images/markdown/11_childdocument_call.PNG b/images/markdown/11_childdocument_call.PNG new file mode 100644 index 00000000..83b74e3f Binary files /dev/null and b/images/markdown/11_childdocument_call.PNG differ diff --git a/images/markdown/12_notsundayanalysis.PNG b/images/markdown/12_notsundayanalysis.PNG new file mode 100644 index 00000000..d7484197 Binary files /dev/null and b/images/markdown/12_notsundayanalysis.PNG differ diff --git a/images/markdown/12_sundayanalysis.PNG b/images/markdown/12_sundayanalysis.PNG new file mode 100644 index 00000000..e16c98b3 Binary files /dev/null and b/images/markdown/12_sundayanalysis.PNG differ diff --git a/images/markdown/1_gettingstartedB.png b/images/markdown/1_gettingstartedB.png index 98117732..32e8b0da 100644 Binary files a/images/markdown/1_gettingstartedB.png and b/images/markdown/1_gettingstartedB.png differ diff --git a/images/quarto/0_quartoimg.PNG b/images/quarto/0_quartoimg.PNG new file mode 100644 index 00000000..23f6305b Binary files /dev/null and b/images/quarto/0_quartoimg.PNG differ diff --git a/images/quarto/1_createquarto.PNG b/images/quarto/1_createquarto.PNG new file mode 100644 index 00000000..cadc11d2 Binary files /dev/null and b/images/quarto/1_createquarto.PNG differ diff --git a/images/quarto/2_namingquarto.PNG b/images/quarto/2_namingquarto.PNG new file mode 100644 index 00000000..b649d87d Binary files /dev/null and b/images/quarto/2_namingquarto.PNG differ diff --git a/images/quarto/3_quartovisual.PNG b/images/quarto/3_quartovisual.PNG new file mode 100644 index 00000000..dab728d2 Binary files /dev/null and b/images/quarto/3_quartovisual.PNG differ diff --git a/images/quarto/4_qmd_to_report.PNG b/images/quarto/4_qmd_to_report.PNG new file mode 100644 index 00000000..c5b116b3 Binary files /dev/null and b/images/quarto/4_qmd_to_report.PNG differ diff --git a/images/quarto/4_quarto_script.png b/images/quarto/4_quarto_script.png new file mode 100644 index 00000000..cb129acc Binary files /dev/null and b/images/quarto/4_quarto_script.png differ diff --git a/images/quarto/5_quarto_report.png b/images/quarto/5_quarto_report.png new file mode 100644 index 00000000..ee9b51d9 Binary files /dev/null and b/images/quarto/5_quarto_report.png differ diff --git a/images/quarto/rstudio-qmd-how-it-works.png b/images/quarto/rstudio-qmd-how-it-works.png new file mode 100644 index 00000000..1c2900c7 Binary files /dev/null and b/images/quarto/rstudio-qmd-how-it-works.png differ diff --git a/index.qmd b/index.qmd index 7a260c5b..2c509b9f 100644 --- a/index.qmd +++ b/index.qmd @@ -54,7 +54,7 @@ knitr::include_graphics(here::here("images", "Epi R Handbook banner beige 1500x5
    [**Written by epidemiologists, for epidemiologists**]{style="color: black;"} -::: {style="display: flex;"} +:::::: {style="display: flex;"}
    ```{r, out.width = "100%", fig.align = "center", echo=F} @@ -73,13 +73,13 @@ a column separator --> [**Applied Epi**](http://www.appliedepi.org) is a nonprofit organisation and grassroots movement of frontline epis from around the world. We write in our spare time to offer this resource to the community. Your encouragement and feedback is most welcome: - Visit our [**website**](http://www.appliedepi.org) and [**join our contact list**](https://forms.gle/9awNd8syypTSYUsn7)\ -- **contact\@appliedepi.org**, tweet [**\@appliedepi**](https://twitter.com/appliedepi), or [**LinkedIn**](www.linkedin.com/company/appliedepi)\ +- **contact\@appliedepi.org**, tweet [**\@appliedepi**](https://twitter.com/appliedepi), or [**LinkedIn**](https://www.linkedin.com/company/appliedepi)\ - Submit issues to our [**Github repository**](https://github.com/appliedepi/epiRhandbook_eng) -**We offer live R training** from instructors with decades of applied epidemiology experience - [www.appliedepi.org/live](www.appliedepi.org/live). +**We offer live R training** from instructors with decades of applied epidemiology experience - [www.appliedepi.org/live](https://appliedepi.org/live/).
    -::: +::::::
    @@ -168,4 +168,4 @@ Batra, Neale, et al. The Epidemiologist R Handbook. 2021. click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -97,9 +97,9 @@ The package **apyramid** is a product of the [R4Epis](https://r4epis.netlify.com Using the cleaned `linelist` dataset, we can create an age pyramid with one simple `age_pyramid()` command. In this command: -* The `data = ` argument is set as the `linelist` data frame -* The `age_group = ` argument (for y-axis) is set to the name of the categorical age column (in quotes) -* The `split_by = ` argument (for x-axis) is set to the gender column +* The `data = ` argument is set as the `linelist` data frame. +* The `age_group = ` argument (for y-axis) is set to the name of the categorical age column (in quotes). +* The `split_by = ` argument (for x-axis) is set to the gender column. ```{r, warning=F, message=F} apyramid::age_pyramid(data = linelist, @@ -151,19 +151,19 @@ apyramid::age_pyramid( proportional = TRUE, # show percents, not counts show_midpoint = FALSE, # remove bar mid-point line #pal = c("orange", "purple") # can specify alt. colors here (but not labels) - )+ + ) + # additional ggplot commands - theme_minimal()+ # simplfy background + theme_minimal() + # simplfy background scale_fill_manual( # specify colors AND labels values = c("orange", "purple"), - labels = c("m" = "Male", "f" = "Female"))+ + labels = c("m" = "Male", "f" = "Female")) + labs(y = "Percent of all cases", # note x and y labs are switched x = "Age categories", fill = "Gender", caption = "My data source and caption here", title = "Title of my plot", - subtitle = "Subtitle with \n a second line...")+ + subtitle = "Subtitle with \n a second line...") + theme( legend.position = "bottom", # legend to bottom axis.text = element_text(size = 10, face = "bold"), # fonts/sizes @@ -188,7 +188,7 @@ demo_agg <- linelist %>% rename(`missing_gender` = `NA`) ``` -...which makes the dataset looks like this: with columns for age category, and male counts, female counts, and missing counts. +Which makes the dataset looks like this: with columns for age category, and male counts, female counts, and missing counts. ```{r, echo=F, warning=F, message=F} # View the aggregated data @@ -289,8 +289,8 @@ A **simple** version of this, using `geom_histogram()`, is below: There are many things you can change/add to this simple version, including: -* Auto adjust counts-axis scale to your data (avoid errors discussed in warning below) -* Manually specify colors and legend labels +* Auto adjust counts-axis scale to your data (avoid errors discussed in warning below). +* Manually specify colors and legend labels. **Convert counts to percents** @@ -325,7 +325,7 @@ Finally we make the `ggplot()` on the percent data. We specify `scale_y_continuo ```{r, warning=F, message=F} # begin ggplot - ggplot()+ # default x-axis is age in years; + ggplot() + # default x-axis is age in years; # case data graph geom_col(data = pyramid_data, @@ -333,10 +333,10 @@ Finally we make the `ggplot()` on the percent data. We specify `scale_y_continuo x = age_cat5, y = percent, fill = gender), - colour = "white")+ # white around each bar + colour = "white") + # white around each bar # flip the X and Y axes to make pyramid vertical - coord_flip()+ + coord_flip() + # adjust the axes scales @@ -349,7 +349,7 @@ Finally we make the `ggplot()` on the percent data. We specify `scale_y_continuo labels = paste0(abs(seq(from = floor(min_per), # sequence of absolute values, by 2s, with "%" to = ceiling(max_per), by = 2)), - "%"))+ + "%")) + # designate colors and legend labels manually scale_fill_manual( @@ -416,9 +416,9 @@ age_levels <- c("0-4","5-9", "10-14", "15-19", "20-24", Combine the population and case data through the **dplyr** function `bind_rows()`: -* First, ensure they have the *exact same* column names, age categories values, and gender values -* Make them have the same data structure: columns of age category, gender, counts, and percent of total -* Bind them together, one on-top of the other (`bind_rows()`) +* First, ensure they have the *exact same* column names, age categories values, and gender values. +* Make them have the same data structure: columns of age category, gender, counts, and percent of total. +* Bind them together, one on-top of the other (`bind_rows()`). @@ -482,21 +482,21 @@ Store the maximum and minimum percent values, used in the plotting function to d ```{r} # Define extent of percent axis, used for plot limits -max_per <- max(pyramid_data$percent, na.rm=T) -min_per <- min(pyramid_data$percent, na.rm=T) +max_per <- max(pyramid_data$percent, na.rm = T) +min_per <- min(pyramid_data$percent, na.rm = T) ``` Now the plot is made with `ggplot()`: -* One bar graph of population data (wider, more transparent bars) -* One bar graph of case data (small, more solid bars) +* One bar graph of population data (wider, more transparent bars). +* One bar graph of case data (small, more solid bars). ```{r, warning=F, message=F} # begin ggplot ############## -ggplot()+ # default x-axis is age in years; +ggplot() + # default x-axis is age in years; # population data graph geom_col( @@ -507,7 +507,7 @@ ggplot()+ # default x-axis is age in years; fill = gender), colour = "black", # black color around bars alpha = 0.2, # more transparent - width = 1)+ # full width + width = 1) + # full width # case data graph geom_col( @@ -518,20 +518,20 @@ ggplot()+ # default x-axis is age in years; fill = gender), # fill of bars by gender colour = "black", # black color around bars alpha = 1, # not transparent - width = 0.3)+ # half width + width = 0.3) + # half width # flip the X and Y axes to make pyramid vertical - coord_flip()+ + coord_flip() + # manually ensure that age-axis is ordered correctly - scale_x_discrete(limits = age_levels)+ # defined in chunk above + scale_x_discrete(limits = age_levels) + # defined in chunk above # set percent-axis scale_y_continuous( limits = c(min_per, max_per), # min and max defined above breaks = seq(floor(min_per), ceiling(max_per), by = 2), # from min% to max% by 2 labels = paste0( # for the labels, paste together... - abs(seq(floor(min_per), ceiling(max_per), by = 2)), "%"))+ + abs(seq(floor(min_per), ceiling(max_per), by = 2)), "%")) + # designate colors and legend labels manually scale_fill_manual( @@ -568,33 +568,6 @@ ggplot()+ # default x-axis is age in years; The techniques used to make a population pyramid with `ggplot()` can also be used to make plots of Likert-scale survey data. -```{r, eval=F, echo=F} -data_raw <- import("P:/Shared/equateur_mve_2020/lessons learned/Ebola After-Action Survey - HQ epi team (form responses).csv") - - -likert_data <- data_raw %>% - select(2, 4:11) %>% - rename(status = 1, - Q1 = 2, - Q2 = 3, - Q3 = 4, - Q4 = 5, - Q5 = 6, - Q6 = 7, - Q7 = 8, - Q8 = 9) %>% - mutate(status = case_when( - stringr::str_detect(status, "Mar") ~ "Senior", - stringr::str_detect(status, "Jan") ~ "Intermediate", - stringr::str_detect(status, "Feb") ~ "Junior", - TRUE ~ "Senior")) %>% - mutate(Q4 = recode(Q4, "Not applicable" = "Very Poor")) - -table(likert_data$status) - -rio::export(likert_data, here::here("data", "likert_data.csv")) -``` - Import the data (see [Download handbook and data](#data-used) page if desired). ```{r echo=F} @@ -616,10 +589,10 @@ DT::datatable(likert_data, rownames = FALSE, filter="top", options = list(pageLe First, some data management steps: -* Pivot the data longer -* Create new column `direction` depending on whether response was generally "positive" or "negative" -* Set the Factor level order for the `status` column and the `Response` column -* Store the max count value so limits of plot are appropriate +* Pivot the data longer. +* Create new column `direction` depending on whether response was generally "positive" or "negative". +* Set the Factor level order for the `status` column and the `Response` column. +* Store the max count value so limits of plot are appropriate. ```{r, warning=F, message=F} @@ -654,7 +627,7 @@ We use `geom_bar()` because our data are one row per observation, not aggregated ```{r, warning=F, message=F} # make plot -ggplot()+ +ggplot() + # bar graph of the "negative" responses geom_bar( @@ -665,7 +638,7 @@ ggplot()+ fill = Response), color = "black", closed = "left", - position = "stack")+ + position = "stack") + # bar graph of the "positive responses geom_bar( @@ -675,13 +648,13 @@ ggplot()+ fill = Response), colour = "black", closed = "left", - position = "stack")+ + position = "stack") + # flip the X and Y axes - coord_flip()+ + coord_flip() + # Black vertical line at 0 - geom_hline(yintercept = 0, color = "black", size=1)+ + geom_hline(yintercept = 0, color = "black", size=1) + # convert labels to all positive numbers scale_y_continuous( @@ -705,22 +678,22 @@ ggplot()+ "Good" = "green3", "Poor" = "yellow", "Very Poor" = "red3"), - breaks = c("Very Good", "Good", "Poor", "Very Poor"))+ # orders the legend + breaks = c("Very Good", "Good", "Poor", "Very Poor")) + # orders the legend # facet the entire plot so each question is a sub-plot - facet_wrap( ~ Question, ncol = 3)+ + facet_wrap( ~ Question, ncol = 3) + # labels, titles, caption labs( title = str_glue("Likert-style responses\nn = {nrow(likert_data)}"), x = "Respondent status", y = "Number of responses", - fill = "")+ + fill = "") + # display adjustments - theme_minimal()+ + theme_minimal() + theme(axis.text = element_text(size = 12), axis.title = element_text(size = 14, face = "bold"), strip.text = element_text(size = 14, face = "bold"), # facet sub-titles diff --git a/new_pages/basics.html b/new_pages/basics.html new file mode 100644 index 00000000..63c2ec9b --- /dev/null +++ b/new_pages/basics.html @@ -0,0 +1,3387 @@ + + + + + + + + + +3  R Basics – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    3  R Basics

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Welcome!

    +

    This page reviews the essentials of R. It is not intended to be a comprehensive tutorial, but it provides the basics and can be useful for refreshing your memory. The section on Resources for learning links to more comprehensive tutorials.

    +

    Parts of this page have been adapted with permission from the R4Epis project.

    +

    See the page on Transition to R for tips on switching to R from STATA, SAS, or Excel.

    + +
    +

    3.1 Why use R?

    +

    As stated on the R project website, R is a programming language and environment for statistical computing and graphics. It is highly versatile, extendable, and community-driven.

    +

    Cost

    +

    R is free to use! There is a strong ethic in the community of free and open-source material.

    +

    Reproducibility

    +

    Conducting your data management and analysis through a programming language (compared to Excel or another primarily point-click/manual tool) enhances reproducibility, makes error-detection easier, and eases your workload.

    +

    Community

    +

    The R community of users is enormous and collaborative. New packages and tools to address real-life problems are developed daily, and vetted by the community of users. As one example, R-Ladies is a worldwide organization whose mission is to promote gender diversity in the R community, and is one of the largest organizations of R users. It likely has a chapter near you!

    +
    +
    +

    3.2 Key terms

    +

    RStudio - RStudio is a Graphical User Interface (GUI) for easier use of R. Read more in the RStudio section.

    +

    Objects - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are objects which are assigned a name and can be referenced in later commands. Read more in the Objects section.

    +

    Functions - A function is a code operation that accept inputs and returns a transformed output. Read more in the Functions section.

    +

    Packages - An R package is a shareable bundle of functions. Read more in the Packages section.

    +

    Scripts - A script is the document file that hold your commands. Read more in the Scripts section.

    +
    +
    +

    3.3 Resources for learning

    +
    +

    Resources within RStudio

    +

    Help documentation

    +

    Search the RStudio “Help” tab for documentation on R packages and specific functions. This is within the pane that also contains Files, Plots, and Packages (typically in the lower-right pane). As a shortcut, you can also type the name of a package or function into the R console after a question-mark to open the relevant Help page. Do not include parentheses.

    +

    For example: ?filter or ?diagrammeR.

    +

    Interactive tutorials

    +

    There are several ways to learn R interactively within RStudio.

    +

    RStudio itself offers a Tutorial pane that is powered by the learnr R package. Simply install this package and open a tutorial via the new “Tutorial” tab in the upper-right RStudio pane (which also contains Environment and History tabs).

    +

    The R package swirl offers interactive courses in the R Console. Install and load this package, then run the command swirl() (empty parentheses) in the R console. You will see prompts appear in the Console. Respond by typing in the Console. It will guide you through a course of your choice.

    +
    +
    +

    Cheatsheets

    +

    There are many PDF “cheatsheets” available on the RStudio website, for example:

    +
      +
    • Factors with forcats package.
      +
    • +
    • Dates and times with lubridate package.
      +
    • +
    • Strings with stringr package.
      +
    • +
    • iterative opertaions with purrr package.
      +
    • +
    • Data import.
      +
    • +
    • Data transformation cheatsheet with dplyr package.
      +
    • +
    • R Markdown (to create documents like PDF, Word, Powerpoint…).
      +
    • +
    • Shiny (to build interactive web apps).
      +
    • +
    • Data visualization with ggplot2 package.
      +
    • +
    • Cartography (GIS).
      +
    • +
    • leaflet package (interactive maps).
      +
    • +
    • Python with R (reticulate package).
    • +
    +

    This is an online R resource specifically for Excel users.

    +
    +
    +

    Twitter

    + +

    Also:

    +

    #epitwitter and #rstats

    +
    +
    +

    Free online resources

    +

    A definitive text is the R for Data Science book by Garrett Grolemund and Hadley Wickham.

    +

    The R4Epis project website aims to “develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting”. You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up.

    +
    +
    +

    Languages other than English

    +

    Materiales de RStudio en Español

    +

    Introduction à R et au tidyverse (Francais)

    + +
    +
    +
    +

    3.4 Installation

    +
    +

    R and RStudio

    +

    How to install R

    +

    Visit this website https://www.r-project.org/ and download the latest version of R suitable for your computer.

    +

    How to install RStudio

    +

    Visit this website https://rstudio.com/products/rstudio/download/ and download the latest free Desktop version of RStudio suitable for your computer.

    +

    Permissions
    +Note that you should install R and RStudio to a drive where you have read and write permissions. Otherwise, your ability to install R packages (a frequent occurrence) will be impacted. If you encounter problems, try opening RStudio by right-clicking the icon and selecting “Run as administrator”. Other tips can be found in the page R on network drives.

    +

    How to update R and RStudio

    +

    Your version of R is printed to the R Console at start-up. You can also run sessionInfo().

    +

    To update R, go to the website mentioned above and re-install R. Alternatively, you can use the installr package (on Windows) by running installr::updateR(). This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the installr documentation.

    +

    Be aware that the old R version will still exist in your computer. You can temporarily run an older version of R by clicking “Tools” -> “Global Options” in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R.

    +

    To update RStudio, you can go to the website above and re-download RStudio. Another option is to click “Help” -> “Check for Updates” within RStudio, but this may not show the very latest updates.

    +

    To see which versions of R, RStudio, or packages were used when this Handbook as made, see the page on Editorial and technical notes.

    +
    +
    +

    Other software you may need to install

    +
      +
    • TinyTeX (for compiling an RMarkdown document to PDF).
      +
    • +
    • Pandoc (for compiling RMarkdown documents).
      +
    • +
    • RTools (for building packages for R).
      +
    • +
    • phantomjs (for saving still images of animated networks, such as transmission chains).
    • +
    +
    +

    TinyTex

    +

    TinyTex is a custom LaTeX distribution, useful when trying to produce PDFs from R.
    +See https://yihui.org/tinytex/ for more informaton.

    +

    To install TinyTex from R:

    +
    +
    install.packages('tinytex')
    +tinytex::install_tinytex()
    +# to uninstall TinyTeX, run tinytex::uninstall_tinytex()
    +
    +
    +
    +

    Pandoc

    +

    Pandoc is a document converter, a separate software from R. It comes bundled with RStudio and should not need to be downloaded. It helps the process of converting Rmarkdown documents to formats like .pdf and adding complex functionality.

    +
    +
    +

    RTools

    +

    RTools is a collection of software for building packages for R

    +

    Install from this website: https://cran.r-project.org/bin/windows/Rtools/

    +
    +
    +

    phantomjs

    +

    This is often used to take “screenshots” of webpages. For example when you make a transmission chain with epicontacts package, an HTML file is produced that is interactive and dynamic. If you want a static image, it can be useful to use the webshot package to automate this process. This will require the external program “phantomjs”. You can install phantomjs via the webshot package with the command webshot::install_phantomjs().

    + +
    +
    +
    +
    +

    3.5 RStudio

    +
    +

    RStudio orientation

    +

    First, open RStudio.

    +

    As their icons can look very similar, be sure you are opening RStudio and not R.

    +

    For RStudio to work you must also have R installed on the computer (see above for installation instructions).

    +

    RStudio is an interface (GUI) for easier use of R. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) here.

    +

    By default RStudio displays four rectangle panes.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    TIP: If your RStudio displays only one left pane it is because you have no scripts open yet.

    +

    The Source Pane
    +This pane, by default in the upper-left, is a space to edit, run, and save your scripts. Scripts contain the commands you want to run. This pane can also display datasets (data frames) for viewing.

    +

    For Stata users, this pane is similar to your Do-file and Data Editor windows.

    +

    The R Console Pane

    +

    The R Console, by default the left or lower-left pane in R Studio, is the home of the R “engine”. This is where the commands are actually run and non-graphic outputs and error/warning messages appear. You can directly enter and run commands in the R Console, but realize that these commands are not saved as they are when running commands from a script.

    +

    If you are familiar with Stata, the R Console is like the Command Window and also the Results Window.

    +

    The Environment Pane
    +This pane, by default in the upper-right, is most often used to see brief summaries of objects in the R Environment in the current session. These objects could include imported, modified, or created datasets, parameters you have defined (e.g. a specific epi week for the analysis), or vectors or lists you have defined during analysis (e.g. names of regions). You can click on the arrow next to a data frame name to see its variables.

    +

    In Stata, this is most similar to the Variables Manager window.

    +

    This pane also contains History where you can see commands that you can previously. It also has a “Tutorial” tab where you can complete interactive R tutorials if you have the learnr package installed. It also has a “Connections” pane for external connections, and can have a “Git” pane if you choose to interface with Github.

    +

    Plots, Viewer, Packages, and Help Pane
    +The lower-right pane includes several important tabs. Typical plot graphics including maps will display in the Plot pane. Interactive or HTML outputs will display in the Viewer pane. The Help pane can display documentation and help files. The Files pane is a browser which can be used to open or delete files. The Packages pane allows you to see, install, update, delete, load/unload R packages, and see which version of the package you have. To learn more about packages see the packages section below.

    +

    This pane contains the Stata equivalents of the Plots Manager and Project Manager windows.

    +
    +
    +

    RStudio settings

    +

    Change RStudio settings and appearance in the Tools drop-down menu, by selecting Global Options. There you can change the default settings, including appearance/background color.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Restart

    +

    If your R freezes, you can re-start R by going to the Session menu and clicking “Restart R”. This avoids the hassle of closing and opening RStudio.

    +

    CAUTION: Everything in your R environment will be removed when you do this.

    +
    +
    +

    Keyboard shortcuts

    +

    Some very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux Rstudio user interface cheatsheet.

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Windows/LinuxMacAction
    EscEscInterrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing “+” in the R console).
    Ctrl+sCmd+sSave (script).
    TabTabAuto-complete.
    Ctrl + EnterCmd + EnterRun current line(s)/selection of code.
    Ctrl + Shift + CCmd + Shift + cComment/uncomment the highlighted lines.
    Alt + -Option + -Insert <-.
    Ctrl + Shift + mCmd + Shift + mInsert %>%.
    Ctrl + lCmd + lClear the R console.
    Ctrl + Alt + bCmd + Option + bRun from start to current. line
    Ctrl + Alt + tCmd + Option + tRun the current code section (R Markdown).
    Ctrl + Alt + iCmd + Shift + rInsert code chunk (into R Markdown).
    Ctrl + Alt + cCmd + Option + cRun current code chunk (R Markdown).
    up/down arrows in R consoleSameToggle through recently run commands.
    Shift + up/down arrows in scriptSameSelect multiple code lines.
    Ctrl + fCmd + fFind and replace in current script.
    Ctrl + Shift + fCmd + Shift + fFind in files (search/replace across many scripts).
    Alt + lCmd + Option + lFold selected code.
    Shift + Alt + lCmd + Shift + Option+lUnfold selected code.
    +

    TIP: Use your Tab key when typing to engage RStudio’s auto-complete functionality. This can prevent spelling errors. Press Tab while typing to produce a drop-down menu of likely functions and objects, based on what you have typed so far.

    + +
    +
    +
    +

    3.6 Functions

    +

    Functions are at the core of using R. Functions are how you perform tasks and operations. Many functions come installed with R, many more are available for download in packages (explained in the packages section), and you can even write your own custom functions!

    +

    This basics section on functions explains:

    +
      +
    • What a function is and how they work.
      +
    • +
    • What function arguments are.
      +
    • +
    • How to get help understanding a function.
    • +
    +

    A quick note on syntax: In this handbook, functions are written in code-text with open parentheses, like this: filter(). As explained in the packages section, functions are downloaded within packages. In this handbook, package names are written in bold, like dplyr. Sometimes in example code you may see the function name linked explicitly to the name of its package with two colons (::) like this: dplyr::filter(). The purpose of this linkage is explained in the packages section.

    + +
    +

    Simple functions

    +

    A function is like a machine that receives inputs, carries out an action with those inputs, and produces an output. What the output is depends on the function.

    +

    Functions typically operate upon some object placed within the function’s parentheses. For example, the function sqrt() calculates the square root of a number:

    +
    +
    sqrt(49)
    +
    +
    [1] 7
    +
    +
    +

    The object provided to a function also can be a column in a dataset (see the Objects section for detail on all the kinds of objects). Because R can store multiple datasets, you will need to specify both the dataset and the column. One way to do this is using the $ notation to link the name of the dataset and the name of the column (dataset$column). In the example below, the function summary() is applied to the numeric column age in the dataset linelist, and the output is a summary of the column’s numeric and missing values.

    +
    +
    # Print summary statistics of column 'age' in the dataset 'linelist'
    +summary(linelist$age)
    +
    +
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
    +   0.00    6.00   13.00   16.07   23.00   84.00      86 
    +
    +
    +

    NOTE: Behind the scenes, a function represents complex additional code that has been wrapped up for the user into one easy command.

    + +
    +
    +

    Functions with multiple arguments

    +

    Functions often ask for several inputs, called arguments, located within the parentheses of the function, usually separated by commas.

    +
      +
    • Some arguments are required for the function to work correctly, others are optional.
      +
    • +
    • Optional arguments have default settings.
      +
    • +
    • Arguments can take character, numeric, logical (TRUE/FALSE), and other inputs.
    • +
    +

    Here is a fun fictional function, called oven_bake(), as an example of a typical function. It takes an input object (e.g. a dataset, or in this example “dough”) and performs operations on it as specified by additional arguments (minutes = and temperature =). The output can be printed to the console, or saved as an object using the assignment operator <-.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    In a more realistic example, the age_pyramid() command below produces an age pyramid plot based on defined age groups and a binary split column, such as gender. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish linelist as the data frame to use, age_cat5 as the column to count, and gender as the binary column to use for splitting the pyramid by color.

    +
    +
    # Create an age pyramid
    +age_pyramid(data = linelist, age_group = "age_cat5", split_by = "gender")
    +
    +
    +
    +

    +
    +
    +
    +
    +

    The above command can be equivalently written as below, in a longer style with a new line for each argument. This style can be easier to read, and easier to write “comments” with # to explain each part (commenting extensively is good practice!). To run this longer command you can highlight the entire command and click “Run”, or just place your cursor in the first line and then press the Ctrl and Enter keys simultaneously.

    +
    +
    # Create an age pyramid
    +age_pyramid(
    +  data = linelist,        # use case linelist
    +  age_group = "age_cat5", # provide age group column
    +  split_by = "gender"     # use gender column for two sides of pyramid
    +  )
    +
    +
    +
    +

    +
    +
    +
    +
    +

    The first half of an argument assignment (e.g. data =) does not need to be specified if the arguments are written in a specific order (specified in the function’s documentation). The below code produces the exact same pyramid as above, because the function expects the argument order: data frame, age_group variable, split_by variable.

    +
    +
    # This command will produce the exact same graphic as above
    +age_pyramid(linelist, "age_cat5", "gender")
    +
    +

    A more complex age_pyramid() command might include the optional arguments to:

    +
      +
    • Show proportions instead of counts (set proportional = TRUE when the default is FALSE)
      +
    • +
    • Specify the two colors to use (pal = is short for “palette” and is supplied with a vector of two color names. See the objects page for how the function c() makes a vector)
    • +
    +

    NOTE: For arguments that you specify with both parts of the argument (e.g. proportional = TRUE), their order among all the arguments does not matter.

    +
    +
    age_pyramid(
    +  linelist,                    # use case linelist
    +  "age_cat5",                  # age group column
    +  "gender",                    # split by gender
    +  proportional = TRUE,         # percents instead of counts
    +  pal = c("orange", "purple")  # colors
    +  )
    +
    +
    +
    +

    +
    +
    +
    +
    +

    TIP: Remember that you can put ? before a function to see what arguments the function can take, and which arguments are needed and which arguments have default values. For example `?age_pyramid’.

    + +
    +
    +

    Writing Functions

    +

    R is a language that is oriented around functions, so you should feel empowered to write your own functions. Creating functions brings several advantages:

    +
      +
    • To facilitate modular programming - the separation of code in to independent and manageable pieces.
      +
    • +
    • Replace repetitive copy-and-paste, which can be error prone.
      +
    • +
    • Give pieces of code memorable names.
    • +
    +

    How to write a function is covered in-depth in the Writing functions page.

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +

    3.7 Packages

    +

    Packages contain functions.

    +

    An R package is a shareable bundle of code and documentation that contains pre-defined functions. Users in the R community develop packages all the time catered to specific problems, it is likely that one can help with your work! You will install and use hundreds of packages in your use of R.

    +

    On installation, R contains “base” packages and functions that perform common elementary tasks. But many R users create specialized functions, which are verified by the R community and which you can download as a package for your own use. In this handbook, package names are written in bold. One of the more challenging aspects of R is that there are often many functions or packages to choose from to complete a given task.

    +
    +

    Install and load

    +

    Functions are contained within packages which can be downloaded (“installed”) to your computer from the internet. Once a package is downloaded, it is stored in your “library”. You can then access the functions it contains during your current R session by “loading” the package.

    +

    Think of R as your personal library: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow, “load”, that book from your library.

    +

    In summary: to use the functions available in an R package, 2 steps must be implemented:

    +
      +
    1. The package must be installed (once), and
      +
    2. +
    3. The package must be loaded (each R session)
    4. +
    +
    +

    Your library

    +

    Your “library” is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called “library”. For example: R\4.4.1\library (the 4.4.1 is the R version - you’ll have a different library for each R version you’ve downloaded).

    +

    You can print the file path to your library by entering .libPaths() (empty parentheses). This becomes especially important if working with R on network drives.

    +
    +
    +

    Install from CRAN

    +

    Most often, R users download packages from CRAN. CRAN (Comprehensive R Archive Network) is an online public warehouse of R packages that have been published by R community members.

    +

    Are you worried about viruses and security when downloading a package from CRAN? Read this article on the topic.

    +
    +
    +

    How to install and load

    +

    In this handbook, we suggest using the pacman package (short for “package manager”). It offers a convenient function p_load() which will install a package if necessary and load it for use in the current R session.

    +

    The syntax quite simple. Just list the names of the packages within the p_load() parentheses, separated by commas. This command will install the rio, tidyverse, and here packages if they are not yet installed, and will load them for use. This makes the p_load() approach convenient and concise if sharing scripts with others.

    +

    Note that package names are case-sensitive.

    +
    +
    # Install (if necessary) and load packages for use
    +pacman::p_load(rio, tidyverse, here)
    +
    +

    Here we have used the syntax pacman::p_load() which explicitly writes the package name (pacman) prior to the function name (p_load()), connected by two colons ::. This syntax is useful because it also loads the pacman package (assuming it is already installed).

    +

    There are alternative base R functions that you will see often. The base R function for installing a package is install.packages(). The name of the package to install must be provided in the parentheses in quotes. If you want to install multiple packages in one command, they must be listed within a character vector c().

    +

    Note: this command installs a package, but does not load it for use in the current session.

    +
    +
    # install a single package with base R
    +install.packages("tidyverse")
    +
    +# install multiple packages with base R
    +install.packages(c("tidyverse", "rio", "here"))
    +
    +

    Installation can also be accomplished point-and-click by going to the RStudio “Packages” pane and clicking “Install” and searching for the desired package name.

    +

    The base R function to load a package for use (after it has been installed) is library(). It can load only one package at a time (another reason to use p_load()). You can provide the package name with or without quotes.

    +
    +
    # load packages for use, with base R
    +library(tidyverse)
    +library(rio)
    +library(here)
    +
    +

    To check whether a package is installed or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session.

    +

    Install from Github

    +

    Sometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the development version with new features not yet offered in the more stable published CRAN version. These are often hosted on the website github.com in a free, public-facing code “repository”. Read more about Github in the handbook page on Version control and collaboration with Git and Github.

    +

    To download R packages from Github, you can use the function p_load_gh() from pacman, which will install the package if necessary, and load it for use in your current R session. Alternatives to install include using the remotes or devtools packages. Read more about all the pacman functions in the package documentation.

    +

    To install from Github, you have to provide more information. You must provide:

    +
      +
    1. The Github ID of the repository owner
    2. +
    3. The name of the repository that contains the package
      +
    4. +
    5. Optional: The name of the “branch” (specific development version) you want to download
    6. +
    +

    In the examples below, the first word in the quotation marks is the Github ID of the repository owner, after the slash is the name of the repository (the name of the package).

    +
    +
    # install/load the epicontacts package from its Github repository
    +p_load_gh("reconhub/epicontacts")
    +
    +

    If you want to install from a “branch” (version) other than the main branch, add the branch name after an “@”, after the repository name.

    +
    +
    # install the "timeline" branch of the epicontacts package from Github
    +p_load_gh("reconhub/epicontacts@timeline")
    +
    +

    If there is no difference between the Github version and the version on your computer, no action will be taken. You can “force” a re-install by instead using p_load_current_gh() with the argument update = TRUE. Read more about pacman in this online vignette

    +

    Install from ZIP or TAR

    +

    You could install the package from a URL:

    +
    +
    packageurl <- "https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz"
    +install.packages(packageurl, repos=NULL, type="source")
    +
    +

    Or, download it to your computer in a zipped file:

    +

    Option 1: using install_local() from the remotes package

    +
    +
    remotes::install_local("~/Downloads/dplyr-master.zip")
    +
    +

    Option 2: using install.packages() from base R, providing the file path to the ZIP file and setting type = "source and repos = NULL.

    +
    +
    install.packages("~/Downloads/dplyr-master.zip", repos=NULL, type="source")
    +
    +
    +
    +
    +

    Code syntax

    +

    For clarity in this handbook, functions are sometimes preceded by the name of their package using the :: symbol in the following way: package_name::function_name()

    +

    Once a package is loaded for a session, this explicit style is not necessary. One can just use function_name(). However writing the package name is useful when a function name is common and may exist in multiple packages (e.g. plot()). Writing the package name will also load the package if it is not already loaded.

    +
    +
    # This command uses the package "rio" and its function "import()" to import a dataset
    +linelist <- rio::import("linelist.xlsx", which = "Sheet1")
    +
    +
    +
    +

    Function help

    +

    To read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like ?thefunctionname (for example, to get help for the function p_load you would write ?p_load) and the Help page will appear in the Help pane. Finally, try searching online for resources.

    +
    +
    +

    Update packages

    +

    You can update packages by re-installing them. You can also click the green “Update” button in your RStudio Packages pane to see which packages have new versions to install. Be aware that your old code may need to be updated if there is a major revision to how a function works!

    +
    +
    +

    Delete packages

    +

    Use p_delete() from pacman, or remove.packages() from base R.

    +
    +
    +

    Dependencies

    +

    Packages often depend on other packages to work. These are called dependencies. If a dependency fails to install, then the package depending on it may also fail to install.

    +

    See the dependencies of a package with p_depends(), and see which packages depend on it with p_depends_reverse()

    +
    +
    +

    Masked functions

    +

    It is not uncommon that two or more packages contain the same function name. For example, the package dplyr has a filter() function, but so does the package stats. The default filter() function depends on the order these packages are first loaded in the R session - the later one will be the default for the command filter().

    +

    You can check the order in your Environment pane of R Studio - click the drop-down for “Global Environment” and see the order of the packages. Functions from packages lower on that drop-down list will mask functions of the same name in packages that appear higher in the drop-down list. When first loading a package, R will warn you in the console if masking is occurring, but this can be easy to miss.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Here are ways you can fix masking:

    +
      +
    1. Specify the package name in the command. For example, use dplyr::filter()
      +
    2. +
    3. Re-arrange the order in which the packages are loaded (e.g. within p_load()), and start a new R session
    4. +
    +
    +
    +

    Detach / unload

    +

    To detach (unload) a package, use this command, with the correct package name and only one colon. Note that this may not resolve masking.

    +
    +
    detach(package:PACKAGE_NAME_HERE, unload=TRUE)
    +
    +
    +
    +

    Install older version

    +

    See this guide to install an older version of a particular package.

    +
    +
    +

    Suggested packages

    +

    See the page on Suggested packages for a listing of packages we recommend for everyday epidemiology.

    + +
    +
    +
    +

    3.8 Scripts

    +

    Scripts are a fundamental part of programming. They are documents that hold your commands (e.g. functions to create and modify datasets, print visualizations, etc). You can save a script and run it again later. There are many advantages to storing and running your commands from a script (vs. typing commands one-by-one into the R console “command line”):

    +
      +
    • Portability - you can share your work with others by sending them your scripts.
      +
    • +
    • Reproducibility - so that you and others know exactly what you did.
      +
    • +
    • Version control - so you can track changes made by yourself or colleagues.
      +
    • +
    • Commenting/annotation - to explain to your colleagues what you have done.
    • +
    +
    +

    Commenting

    +

    In a script you can also annotate (“comment”) around your R code. Commenting is helpful to explain to yourself and other readers what you are doing. You can add a comment by typing the hash symbol (#) and writing your comment after it. The commented text will appear in a different color than the R code.

    +

    Any code written after the # will not be run. Therefore, placing a # before code is also a useful way to temporarily block a line of code (“comment out”) if you do not want to delete it. You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac).

    +
    +
    # A comment can be on a line by itself
    +# import data
    +linelist <- import("linelist_raw.xlsx") %>%   # a comment can also come after code
    +# filter(age > 50)                          # It can also be used to deactivate / remove a line of code
    +  count()
    +
    +

    There are a few general ideas to follow when writing your scripts in order to make them accessible. - Add comments on what you are doing and on why you are doing it.
    +- Break your code into logical sections.
    +- Accompany your code with a text step-by-step description of what you are doing (e.g. numbered steps).

    +
    +
    +

    Style

    +

    It is important to be conscious of your coding style - especially if working on a team. We advocate for the tidyverse style guide. There are also packages such as styler and lintr which help you conform to this style.

    +

    A few very basic points to make your code readable to others:
    +* When naming objects, use only lowercase letters, numbers, and underscores _, e.g. my_data
    +* Use frequent spaces, including around operators, e.g. n = 1 and age_new <- age_old + 3

    +
    +
    +

    Example Script

    +

    Below is an example of a short R script. Remember, the better you succinctly explain your code in comments, the more your colleagues will like you!

    +
    +
    +
    +
    +

    +
    +
    +
    +
    + +
    +
    +

    R markdown and Quarto

    +

    An R Markdown or Quarto script are types of R script in which the script itself becomes an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports.

    +

    Even this website and handbook is produced with Quarto scripts!

    +

    It is worth noting that beginner R users can also use R Markdown - do not be intimidated! To learn more, see the handbook page on Reports with R Markdown documents.

    + +
    +
    +

    R notebooks

    +

    There is no difference between writing in a Rmarkdown vs an R notebook. However the execution of the document differs slightly. See this site for more details.

    + +
    +
    +

    Shiny

    +

    Shiny apps/websites are contained within one script, which must be named app.R. This file has three components:

    +
      +
    1. A user interface (ui).
      +
    2. +
    3. A server function.
      +
    4. +
    5. A call to the shinyApp function.
    6. +
    +

    See the handbook page on Dashboards with Shiny, or this online tutorial: Shiny tutorial

    +

    In previous versions, the above file was split into two files (ui.R and server.R)

    +
    +
    +

    Code folding

    +

    You can collapse portions of code to make your script easier to read.

    +

    To do this, create a text header with #, write your header, and follow it with at least 4 of either dashes (-), hashes (#) or equals (=). When you have done this, a small arrow will appear in the “gutter” to the left (by the row number). You can click this arrow and the code below until the next header will collapse and a dual-arrow icon will appear in its place.

    +

    To expand the code, either click the arrow in the gutter again, or the dual-arrow icon. There are also keyboard shortcuts as explained in the RStudio section of this page.

    +

    By creating headers with #, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more # symbols, for example # for primary, ## for secondary, and ### for tertiary headers.

    +

    Below are two versions of an example script. On the left is the original with commented headers. On the right, four dashes have been written after each header, making them collapsible. Two of them have been collapsed, and you can see that the Table of Contents at the bottom now shows each section.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Other areas of code that are automatically eligible for folding include “braced” regions with brackets { } such as function definitions or conditional blocks (if else statements). You can read more about code folding at the RStudio site.

    + + + +
    +
    +
    +

    3.9 Working directory

    +

    The working directory is the root folder location used by R for your work - where R looks for and saves files by default. By default, it will save new files and outputs to this location, and will look for files to import (e.g. datasets) here as well.

    +

    The working directory appears in grey text at the top of the RStudio Console pane. You can also print the current working directory by running getwd() (leave the parentheses empty).

    +
    +
    +
    +
    +

    +
    +
    +
    +
    + +
    +

    Set by command

    +

    Until recently, many people learning R were taught to begin their scripts with a setwd() command.

    +

    Please instead consider using an R project-oriented workflow and read the reasons for not using setwd().

    +

    In brief, your work becomes specific to your computer. This means that file paths used to import and export files need to be changed if used on a different computer, or by different collaborators.

    +

    As noted above, although we do not recommend this approach in most circumstances, you can use the command setwd() with the desired folder file path in quotations, for example:

    +
    +
    setwd("C:/Documents/R Files/My analysis")
    +
    +

    DANGER: Setting a working directory with setwd() can be “brittle” if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory, such as with the [here package].

    + +
    +
    +

    Set manually

    +

    To set the working directory manually (the point-and-click equivalent of setwd()), click the Session drop-down menu and go to “Set Working Directory” and then “Choose Directory”. This will set the working directory for that specific R session. Note: if using this approach, you will have to do this manually each time you open RStudio.

    + +
    +
    +

    Within an R project

    +

    If using an R project, the working directory will default to the R project root folder that contains the “.rproj” file. This will apply if you open RStudio by clicking open the R Project (the file with “.rproj” extension).

    + +
    +
    +

    Working directory in an R markdown

    +

    In an R markdown script, the default working directory is the folder the Rmarkdown file (.Rmd) is saved within. If using an R project and here package, this does not apply and the working directory will be here() as explained in the R projects page.

    +

    If you want to change the working directory of a stand-alone R markdown (not in an R project), if you use setwd() this will only apply to that specific code chunk. To make the change for all code chunks in an R markdown, edit the setup chunk to add the root.dir = parameter, such as below:

    +
    +
    knitr::opts_knit$set(root.dir = 'desired/directorypath')
    +
    +

    It is much easier to just use the R markdown within an R project and use the here package.

    + +
    +
    +

    Providing file paths

    +

    Perhaps the most common source of frustration for an R beginner (at least on a Windows machine) is typing in a file path to import or export data. There is a thorough explanation of how to best input file paths in the Import and export page, but here are a few key points:

    +

    Broken paths

    +

    Below is an example of an “absolute” or “full address” file path. These will likely break if used by another computer. One exception is if you are using a shared/network drive.

    +
    C:/Users/Name/Document/Analytic Software/R/Projects/Analysis2019/data/March2019.csv  
    +

    Slash direction

    +

    If typing in a file path, be aware the direction of the slashes.

    +

    Use forward slashes (/) to separate the components (“data/provincial.csv”). For Windows users, the default way that file paths are displayed is with back slashes (\) - so you will need to change the direction of each slash. If you use the here package as described in the R projects page the slash direction is not an issue.

    +

    Relative file paths

    +

    We generally recommend providing “relative” filepaths instead - that is, the path relative to the root of your R Project. You can do this using the here package as explained in the R projects page. A relativel filepath might look like this:

    +
    +
    # Import csv linelist from the data/linelist/clean/ sub-folders of an R project
    +linelist <- import(here("data", "clean", "linelists", "marin_country.csv"))
    +
    +

    Even if using relative file paths within an R project, you can still use absolute paths to import and export data outside your R project.

    + +
    +
    +
    +

    3.10 Objects

    +

    Everything in R is an object, and R is an “object-oriented” language. These sections will explain:

    +
      +
    • How to create objects (<-).
    • +
    • Types of objects (e.g. data frames, vectors..).
      +
    • +
    • How to access subparts of objects (e.g. variables in a dataset).
      +
    • +
    • Classes of objects (e.g. numeric, logical, integer, double, character, factor).
    • +
    + +
    +

    Everything is an object

    +

    This section is adapted from the R4Epis project.
    +

    +

    Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are objects which are assigned a name and can be referenced in later commands.

    +

    An object exists when you have assigned it a value (see the assignment section below). When it is assigned a value, the object appears in the Environment (see the upper right pane of RStudio). It can then be operated upon, manipulated, changed, and re-defined.

    + +
    +
    +

    Defining objects (<-)

    +

    Create objects by assigning them a value with the <- operator.
    +You can think of the assignment operator <- as the words “is defined as”. Assignment commands generally follow a standard order:

    +

    object_name <- value (or process/calculation that produce a value)

    +

    For example, you may want to record the current epidemiological reporting week as an object for reference in later code. In this example, the object current_week is created when it is assigned the value "2018-W10" (the quote marks make this a character value). The object current_week will then appear in the RStudio Environment pane (upper-right) and can be referenced in later commands.

    +

    See the R commands and their output in the boxes below.

    +
    +
    current_week <- "2018-W10"   # this command creates the object current_week by assigning it a value
    +current_week                 # this command prints the current value of current_week object in the console
    +
    +
    [1] "2018-W10"
    +
    +
    +

    NOTE: Note the [1] in the R console output is simply indicating that you are viewing the first item of the output

    +

    CAUTION: An object’s value can be over-written at any time by running an assignment command to re-define its value. Thus, the order of the commands run is very important.

    +

    The following command will re-define the value of current_week:

    +
    +
    current_week <- "2018-W51"   # assigns a NEW value to the object current_week
    +current_week                 # prints the current value of current_week in the console
    +
    +
    [1] "2018-W51"
    +
    +
    +

    Equals signs =

    +

    You will also see equals signs in R code:

    +
      +
    • A double equals sign == between two objects or values asks a logical question: “is this equal to that?”.
      +
    • +
    • You will also see equals signs within functions used to specify values of function arguments (read about these in sections below), for example max(age, na.rm = TRUE).
      +
    • +
    • You can use a single equals sign = in place of <- to create and define objects, but this is discouraged. You can read about why this is discouraged here.
    • +
    +

    Datasets

    +

    Datasets are also objects (typically “data frames”) and must be assigned names when they are imported. In the code below, the object linelist is created and assigned the value of a CSV file imported with the rio package and its import() function.

    +
    +
    # linelist is created and assigned the value of the imported CSV file
    +linelist <- import("my_linelist.csv")
    +
    +

    You can read more about importing and exporting datasets with the section on Import and export.

    +

    CAUTION: A quick note on naming of objects:

    +
      +
    • Object names must not contain spaces, but you should use underscore (_) or a period (.) instead of a space.
      +
    • +
    • Object names are case-sensitive (meaning that Dataset_A is different from dataset_A).
    • +
    • Object names must begin with a letter (they cannot begin with a number like 1, 2 or 3).
    • +
    +

    Outputs

    +

    Outputs like tables and plots provide an example of how outputs can be saved as objects, or just be printed without being saved. A cross-tabulation of gender and outcome using the base R function table() can be printed directly to the R console (without being saved).

    +
    +
    # printed to R console only
    +table(linelist$gender, linelist$outcome)
    +
    +
       
    +    Death Recover
    +  f  1227     953
    +  m  1228     950
    +
    +
    +

    But the same table can be saved as a named object. Then, optionally, it can be printed.

    +
    +
    # save
    +gen_out_table <- table(linelist$gender, linelist$outcome)
    +
    +# print
    +gen_out_table
    +
    +
       
    +    Death Recover
    +  f  1227     953
    +  m  1228     950
    +
    +
    +

    Columns

    +

    Columns in a dataset are also objects and can be defined, over-written, and created as described below in the section on Columns.

    +

    You can use the assignment operator from base R to create a new column. Below, the new column bmi (Body Mass Index) is created, and for each row the new value is result of a mathematical operation on the row’s value in the wt_kg and ht_cm columns.

    +
    +
    # create new "bmi" column using base R syntax
    +linelist$bmi <- linelist$wt_kg / (linelist$ht_cm/100)^2
    +
    +

    However, in this handbook, we emphasize a different approach to defining columns, which uses the function mutate() from the dplyr package and piping with the pipe operator (%>%). The syntax is easier to read and there are other advantages explained in the page on Cleaning data and core functions. You can read more about piping in the Piping section below.

    +
    +
    # create new "bmi" column using dplyr syntax
    +linelist <- linelist %>% 
    +  mutate(bmi = wt_kg / (ht_cm/100)^2)
    +
    + +
    +
    +

    Object structure

    +

    Objects can be a single piece of data (e.g. my_number <- 24), or they can consist of structured data.

    +

    The graphic below is borrowed from this online R tutorial. It shows some common data structures and their names. Not included in this image is spatial data, which is discussed in the GIS basics page.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    In epidemiology (and particularly field epidemiology), you will most commonly encounter data frames and vectors:

    + +++++ + + + + + + + +
    Common structureExplanationExample
    +
    Vectors | A container for a sequence of singular objects, all of the same class (e.g. numeric, character). | “Variables” (columns) in data frames are vectors (e.g. the column age_years). |
    + +++++ + + + + + + + +
    Data FramesVectors (e.g. columns) that are bound together that all have the same number of rows.linelist is a data frame.
    +

    Note that to create a vector that “stands alone” (is not part of a data frame) the function c() is used to combine the different elements. For example, if creating a vector of colors plot’s color scale: vector_of_colors <- c("blue", "red2", "orange", "grey")

    + +
    +
    +

    Object classes

    +

    All the objects stored in R have a class which tells R how to handle the object. There are many possible classes, but common ones include:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ClassExplanationExamples
    CharacterThese are text/words/sentences “within quotation marks”. Math cannot be done on these objects.“Character objects are in quotation marks”
    IntegerNumbers that are whole only (no decimals)-5, 14, or 2000
    NumericThese are numbers and can include decimals. If within quotation marks they will be considered character class.23.1 or 14
    FactorThese are vectors that have a specified order or hierarchy of valuesAn variable of economic status with ordered values
    DateOnce R is told that certain data are Dates, these data can be manipulated and displayed in special ways. See the page on Working with dates for more information.2018-04-12 or 15/3/1954 or Wed 4 Jan 1980
    LogicalValues must be one of the two special values TRUE or FALSE (note these are not “TRUE” and “FALSE” in quotation marks)TRUE or FALSE
    data.frameA data frame is how R stores a typical dataset. It consists of vectors (columns) of data bound together, that all have the same number of observations (rows).The example AJS dataset named linelist_raw contains 68 variables with 300 observations (rows) each.
    tibbletibbles are a variation on data frame, the main operational difference being that they print more nicely to the console (display first 10 rows and only columns that fit on the screen)Any data frame, list, or matrix can be converted to a tibble with as_tibble()
    listA list is like vector, but holds other objects that can be other different classesA list could hold a single number, and a data frame, and a vector, and even another list within it!
    +

    You can test the class of an object by providing its name to the function class(). Note: you can reference a specific column within a dataset using the $ notation to separate the name of the dataset and the name of the column.

    +
    +
    class(linelist)         # class should be a data frame or tibble
    +
    +
    [1] "data.frame"
    +
    +
    class(linelist$age)     # class should be numeric
    +
    +
    [1] "numeric"
    +
    +
    class(linelist$gender)  # class should be character
    +
    +
    [1] "character"
    +
    +
    +

    Sometimes, a column will be converted to a different class automatically by R. Watch out for this! For example, if you have a vector or column of numbers, but a character value is inserted… the entire column will change to class character.

    +
    +
    num_vector <- c(1, 2, 3, 4, 5) # define vector as all numbers
    +class(num_vector)          # vector is numeric class
    +
    +
    [1] "numeric"
    +
    +
    num_vector[3] <- "three"   # convert the third element to a character
    +class(num_vector)          # vector is now character class
    +
    +
    [1] "character"
    +
    +
    +

    One common example of this is when manipulating a data frame in order to print a table - if you make a total row and try to paste/glue together percents in the same cell as numbers (e.g. 23 (40%)), the entire numeric column above will convert to character and can no longer be used for mathematical calculations.Sometimes, you will need to convert objects or columns to another class.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FunctionAction
    as.character()Converts to character class
    as.numeric()Converts to numeric class
    as.integer()Converts to integer class
    as.Date()Converts to Date class - Note: see section on dates for details
    factor()Converts to factor - Note: re-defining order of value levels requires extra arguments
    +

    Likewise, there are base R functions to check whether an object IS of a specific class, such as is.numeric(), is.character(), is.double(), is.factor(), is.integer()

    +

    Here is more online material on classes and data structures in R.

    + +
    +
    +

    Columns/Variables ($)

    +

    A column in a data frame is technically a “vector” (see table above) - a series of values that must all be the same class (either character, numeric, logical, etc).

    +

    A vector can exist independent of a data frame, for example a vector of column names that you want to include as explanatory variables in a model. To create a “stand alone” vector, use the c() function as below:

    +
    +
    # define the stand-alone vector of character values
    +explanatory_vars <- c("gender", "fever", "chills", "cough", "aches", "vomit")
    +
    +# print the values in this named vector
    +explanatory_vars
    +
    +
    [1] "gender" "fever"  "chills" "cough"  "aches"  "vomit" 
    +
    +
    +

    Columns in a data frame are also vectors and can be called, referenced, extracted, or created using the $ symbol. The $ symbol connects the name of the column to the name of its data frame. In this handbook, we try to use the word “column” instead of “variable”.

    +
    +
    # Retrieve the length of the vector age_years
    +length(linelist$age) # (age is a column in the linelist data frame)
    +
    +

    By typing the name of the data frame followed by $ you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes!

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    ADVANCED TIP: Some more complex objects (e.g. a list, or an epicontacts object) may have multiple levels which can be accessed through multiple dollar signs. For example epicontacts$linelist$date_onset

    + +
    +
    +

    Access/index with brackets ([ ])

    +

    You may need to view parts of objects, also called “indexing”, which is often done using the square brackets [ ]. Using $ on a data frame to access a column is also a type of indexing.

    +
    +
    my_vector <- c("a", "b", "c", "d", "e", "f")  # define the vector
    +my_vector[5]                                  # print the 5th element
    +
    +
    [1] "e"
    +
    +
    +

    Square brackets also work to return specific parts of an returned output, such as the output of a summary() function:

    +
    +
    # All of the summary
    +summary(linelist$age)
    +
    +
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
    +   0.00    6.00   13.00   16.07   23.00   84.00      86 
    +
    +
    # Just the second element of the summary, with name (using only single brackets)
    +summary(linelist$age)[2]
    +
    +
    1st Qu. 
    +      6 
    +
    +
    # Just the second element, without name (using double brackets)
    +summary(linelist$age)[[2]]
    +
    +
    [1] 6
    +
    +
    # Extract an element by name, without showing the name
    +summary(linelist$age)[["Median"]]
    +
    +
    [1] 13
    +
    +
    +

    Brackets also work on data frames to view specific rows and columns. You can do this using the syntax data frame[rows, columns]:

    +
    +
    # View a specific row (2) from dataset, with all columns (don't forget the comma!)
    +linelist[2,]
    +
    +# View all rows, but just one column
    +linelist[, "date_onset"]
    +
    +# View values from row 2 and columns 5 through 10
    +linelist[2, 5:10] 
    +
    +# View values from row 2 and columns 5 through 10 and 18
    +linelist[2, c(5:10, 18)] 
    +
    +# View rows 2 through 20, and specific columns
    +linelist[2:20, c("date_onset", "outcome", "age")]
    +
    +# View rows and columns based on criteria
    +# *** Note the data frame must still be named in the criteria!
    +linelist[linelist$age > 25 , c("date_onset", "outcome", "age")]
    +
    +# Use View() to see the outputs in the RStudio Viewer pane (easier to read) 
    +# *** Note the capital "V" in View() function
    +View(linelist[2:20, "date_onset"])
    +
    +# Save as a new object
    +new_table <- linelist[2:20, c("date_onset")] 
    +
    +

    Note that you can also achieve the above row/column indexing on data frames and tibbles using dplyr syntax (functions filter() for rows, and select() for columns). Read more about these core functions in the Cleaning data and core functions page.

    +

    To filter based on “row number”, you can use the dplyr function row_number() with open parentheses as part of a logical filtering statement. Often you will use the %in% operator and a range of numbers as part of that logical statement, as shown below. To see the first N rows, you can also use the special dplyr function head(). Note, there is a function head() from base R, but this is overwritten by the dplyr function when you load tidyverse.

    +
    +
    # View first 100 rows
    +linelist %>% head(100)
    +
    +# Show row 5 only
    +linelist %>% filter(row_number() == 5)
    +
    +# View rows 2 through 20, and three specific columns (note no quotes necessary on column names)
    +linelist %>% 
    +     filter(row_number() %in% 2:20) %>% 
    +     select(date_onset, outcome, age)
    +
    +

    When indexing an object of class list, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list. Brackets can also be written after one another, as demonstrated below.

    +

    This visual explanation of lists indexing, with pepper shakers is humorous and helpful.

    +
    +
    # define demo list
    +my_list <- list(
    +  # First element in the list is a character vector
    +  hospitals = c("Central", "Empire", "Santa Anna"),
    +  
    +  # second element in the list is a data frame of addresses
    +  addresses   = data.frame(
    +    street = c("145 Medical Way", "1048 Brown Ave", "999 El Camino"),
    +    city   = c("Andover", "Hamilton", "El Paso")
    +    )
    +  )
    +
    +

    Here is how the list looks when printed to the console. See how there are two named elements:

    +
      +
    • hospitals, a character vector
      +
    • +
    • addresses, a data frame of addresses
    • +
    +
    +
    my_list
    +
    +
    $hospitals
    +[1] "Central"    "Empire"     "Santa Anna"
    +
    +$addresses
    +           street     city
    +1 145 Medical Way  Andover
    +2  1048 Brown Ave Hamilton
    +3   999 El Camino  El Paso
    +
    +
    +

    Now we extract, using various methods:

    +
    +
    my_list[1] # this returns the element in class "list" - the element name is still displayed
    +
    +
    $hospitals
    +[1] "Central"    "Empire"     "Santa Anna"
    +
    +
    my_list[[1]] # this returns only the (unnamed) character vector
    +
    +
    [1] "Central"    "Empire"     "Santa Anna"
    +
    +
    my_list[["hospitals"]] # you can also index by name of the list element
    +
    +
    [1] "Central"    "Empire"     "Santa Anna"
    +
    +
    my_list[[1]][3] # this returns the third element of the "hospitals" character vector
    +
    +
    [1] "Santa Anna"
    +
    +
    my_list[[2]][1] # This returns the first column ("street") of the address data frame
    +
    +
               street
    +1 145 Medical Way
    +2  1048 Brown Ave
    +3   999 El Camino
    +
    +
    + +
    +
    +

    Remove objects

    +

    You can remove individual objects from your R environment by putting the name in the rm() function (no quote marks):

    +
    +
    rm(object_name)
    +
    +

    You can remove all objects (clear your workspace) by running:

    +
    +
    rm(list = ls(all = TRUE))
    +
    + + + +
    +
    +
    +

    3.11 Piping (%>%)

    +

    Two general approaches to working with objects are:

    +
      +
    1. Pipes/tidyverse - pipes send an object from function to function - emphasis is on the action, not the object.
      +
    2. +
    3. Define intermediate objects - an object is re-defined again and again - emphasis is on the object.
    4. +
    + +
    +

    Pipes

    +

    Simply explained, the pipe operator passes an intermediate output from one function to the next.
    +You can think of it as saying “and then”. Many functions can be linked together with %>%.

    +
      +
    • Piping emphasizes a sequence of actions, not the object the actions are being performed on.
      +
    • +
    • Pipes are best when a sequence of actions must be performed on one object.
      +
    • +
    • Pipes can make code more clean and easier to read, more intuitive.
    • +
    +

    Pipe operators were first introduced through the magrittr package, which is part of tidyverse, and were specified as %>%. In R 4.1.0, they introduced a base R pipe which is specified through |>. The behaviour of the two pipes is the same, and they can be used somewhat interchangeably. However, there are a few key differences.

    +
      +
    • The %>% pipe allows you to pass multiple arguments.
    • +
    • The %>% pipe lets you drop parentheses when calling a function with no other arguments (i.e. drop vs drop()).
    • +
    • The %>% pipe allows you to start a pipe with . to create a function in your linking of code.
    • +
    +

    For these reasons, we recommend the magrittr pipe, %>%, over the base R pipe, |>.

    +

    To read more about the differences between base R and tidyverse (magrittr) pipes, see this blog post. For more information on the tidyverse approach, please see this style guide.

    +

    Here is a fake example for comparison, using fictional functions to “bake a cake”. First, the pipe method:

    +
    +
    # A fake example of how to bake a cake using piping syntax
    +
    +cake <- flour %>%       # to define cake, start with flour, and then...
    +  add(eggs) %>%   # add eggs
    +  add(oil) %>%    # add oil
    +  add(water) %>%  # add water
    +  mix_together(         # mix together
    +    utensil = spoon,
    +    minutes = 2) %>%    
    +  bake(degrees = 350,   # bake
    +       system = "fahrenheit",
    +       minutes = 35) %>%  
    +  let_cool()            # let it cool down
    +
    +

    Note that just like other R commands, pipes can be used to just display the result, or to save/re-save an object, depending on whether the assignment operator <- is involved. See both below:

    +
    +
    # Create or overwrite object, defining as aggregate counts by age category (not printed)
    +linelist_summary <- linelist %>% 
    +  count(age_cat)
    +
    +
    +
    # Print the table of counts in the console, but don't save it
    +linelist %>% 
    +  count(age_cat)
    +
    +
      age_cat    n
    +1     0-4 1095
    +2     5-9 1095
    +3   10-14  941
    +4   15-19  743
    +5   20-29 1073
    +6   30-49  754
    +7   50-69   95
    +8     70+    6
    +9    <NA>   86
    +
    +
    +

    %<>%
    +This is an “assignment pipe” from the magrittr package, which pipes an object forward and also re-defines the object. It must be the first pipe operator in the chain. It is shorthand. The below two commands are equivalent:

    +
    +
    linelist <- linelist %>%
    +  filter(age > 50)
    +
    +linelist %<>% filter(age > 50)
    +
    + +
    +
    +

    Define intermediate objects

    +

    This approach to changing objects/data frames may be better if:

    +
      +
    • You need to manipulate multiple objects
      +
    • +
    • There are intermediate steps that are meaningful and deserve separate object names
    • +
    +

    Risks:

    +
      +
    • Creating new objects for each step means creating lots of objects. If you use the wrong one you might not realize it!
      +
    • +
    • Naming all the objects can be confusing.
      +
    • +
    • Errors may not be easily detectable.
    • +
    +

    Either name each intermediate object, or overwrite the original, or combine all the functions together. All come with their own risks.

    +

    Below is the same fake “cake” example as above, but using this style:

    +
    +
    # a fake example of how to bake a cake using this method (defining intermediate objects)
    +batter_1 <- left_join(flour, eggs)
    +batter_2 <- left_join(batter_1, oil)
    +batter_3 <- left_join(batter_2, water)
    +
    +batter_4 <- mix_together(object = batter_3, utensil = spoon, minutes = 2)
    +
    +cake <- bake(batter_4, degrees = 350, system = "fahrenheit", minutes = 35)
    +
    +cake <- let_cool(cake)
    +
    +

    Combine all functions together - this is difficult to read:

    +
    +
    # an example of combining/nesting mutliple functions together - difficult to read
    +cake <- let_cool(bake(mix_together(batter_3, utensil = spoon, minutes = 2), degrees = 350, system = "fahrenheit", minutes = 35))
    +
    + +
    +
    +
    +

    3.12 Key operators and functions

    +

    This section details operators in R, such as:

    +
      +
    • Definitional operators.
      +
    • +
    • Relational operators (less than, equal too..).
      +
    • +
    • Logical operators (and, or…).
      +
    • +
    • Handling missing values.
      +
    • +
    • Mathematical operators and functions (+/-, >, sum(), median(), …).
      +
    • +
    • The %in% operator.
    • +
    + +
    +

    Assignment operators

    +

    <-

    +

    The basic assignment operator in R is <-. Such that object_name <- value.
    +This assignment operator can also be written as =. We advise use of <- for general R use. We also advise surrounding such operators with spaces, for readability.

    +

    <<-

    +

    If Writing functions, or using R in an interactive way with sourced scripts, then you may need to use this assignment operator <<- (from base R). This operator is used to define an object in a higher ‘parent’ R Environment. See this online reference.

    +

    %<>%

    +

    This is an “assignment pipe” from the magrittr package, which pipes an object forward and also re-defines the object. It must be the first pipe operator in the chain. It is shorthand, as shown below in two equivalent examples:

    +
    +
    linelist <- linelist %>% 
    +  mutate(age_months = age_years * 12)
    +
    +

    The above is equivalent to the below:

    +
    +
    linelist %<>% mutate(age_months = age_years * 12)
    +
    +

    %<+%

    +

    This is used to add data to phylogenetic trees with the ggtree package. See the page on Phylogenetic trees or this online resource book.

    + +
    +
    +

    Relational and logical operators

    +

    Relational operators compare values and are often used when defining new variables and subsets of datasets. Here are the common relational operators in R:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MeaningOperatorExampleExample Result
    Equal to=="A" == "a"FALSE (because R is case sensitive) Note that == (double equals) is different from = (single equals), which acts like the assignment operator <-
    Not equal to!=2 != 0TRUE
    Greater than>4 > 2TRUE
    Less than<4 < 2FALSE
    Greater than or equal to>=6 >= 4TRUE
    Less than or equal to<=6 <= 4FALSE
    Value is missingis.na()is.na(7)FALSE (see page on Missing data)
    Value is not missing!is.na()!is.na(7)TRUE
    +

    Logical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses ( ) for grouping and order of application.

    + ++++ + + + + + + + + + + + + + + + + + + + + +
    MeaningOperator
    AND&
    OR| (vertical bar)
    Parentheses( ) Used to group criteria together and clarify order of operations
    +

    For example, below, we have a linelist with two variables we want to use to create our case definition, hep_e_rdt, a test result and other_cases_in_hh, which will tell us if there are other cases in the household. The command below uses the function case_when() to create the new variable case_def such that:

    +
    +
    linelist_cleaned <- linelist %>%
    +  mutate(case_def = case_when(
    +    is.na(rdt_result) & is.na(other_case_in_home)            ~ NA_character_,
    +    rdt_result == "Positive"                                 ~ "Confirmed",
    +    rdt_result != "Positive" & other_cases_in_home == "Yes"  ~ "Probable",
    +    TRUE                                                     ~ "Suspected"
    +  ))
    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    Criteria in example aboveResulting value in new variable “case_def”
    If the value for variables rdt_result and other_cases_in_home are missingNA (missing)
    If the value in rdt_result is “Positive”“Confirmed”
    If the value in rdt_result is NOT “Positive” AND the value in other_cases_in_home is “Yes”“Probable”
    If one of the above criteria are not met“Suspected”
    +

    Note that R is case-sensitive, so “Positive” is different than “positive”.

    + +
    +
    +

    Missing values

    +

    In R, missing values are represented by the special value NA (a “reserved” value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, “Missing”), you may want to re-code those values to NA. How to do this is addressed in the Import and export page.

    +

    To test whether a value is NA, use the special function is.na(), which returns TRUE or FALSE.

    +
    +
    rdt_result <- c("Positive", "Suspected", "Positive", NA)   # two positive cases, one suspected, and one unknown
    +is.na(rdt_result)  # Tests whether the value of rdt_result is NA
    +
    +
    [1] FALSE FALSE FALSE  TRUE
    +
    +
    +

    Read more about missing, infinite, NULL, and impossible values in the page on Missing data. Learn how to convert missing values when importing data in the page on Import and export.

    + +
    +
    +

    Mathematics and statistics

    +

    All the operators and functions in this page are automatically available using base R.

    +
    +

    Mathematical operators

    +

    These are often used to perform addition, division, to create new columns, etc. Below are common mathematical operators in R. Whether you put spaces around the operators is not important.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PurposeExample in R
    addition2 + 3
    subtraction2 - 3
    multiplication2 * 3
    division30 / 5
    exponent2^3
    order of operations( )
    +
    +
    +

    Mathematical functions

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PurposeFunction
    roundinground(x, digits = n)
    roundingjanitor::round_half_up(x, digits = n)
    ceiling (round up)ceiling(x)
    floor (round down)floor(x)
    absolute valueabs(x)
    square rootsqrt(x)
    exponentexponent(x)
    natural logarithmlog(x)
    log base 10log10(x)
    log base 2log2(x)
    +

    Note: for round() the digits = specifies the number of decimal placed. Use signif() to round to a number of significant figures.

    +
    +
    +

    Scientific notation

    +

    The likelihood of scientific notation being used depends on the value of the scipen option.

    +

    From the documentation of ?options: scipen is a penalty to be applied when deciding to print numeric values in fixed or exponential notation. Positive values bias towards fixed and negative towards scientific notation: fixed notation will be preferred unless it is more than ‘scipen’ digits wider.

    +

    If it is set to a low number (e.g. 0) it will be “turned on” always. To “turn off” scientific notation in your R session, set it to a very high number, for example:

    +
    +
    # turn off scientific notation
    +options(scipen = 999)
    +
    +
    +
    +

    Rounding

    +

    DANGER: round() uses “banker’s rounding” which rounds up from a .5 only if the upper number is even. Use round_half_up() from janitor to consistently round halves up to the nearest whole number. See this explanation

    +
    +
    # use the appropriate rounding function for your work
    +round(c(2.5, 3.5))
    +
    +
    [1] 2 4
    +
    +
    janitor::round_half_up(c(2.5, 3.5))
    +
    +
    [1] 3 4
    +
    +
    +

    For rounding from proportion to percentages, you can use the function percent() from the scales package.

    +
    +
    scales::percent(c(0.25, 0.35), accuracy = 0.1)
    +
    +
    [1] "25.0%" "35.0%"
    +
    +
    +
    +
    +

    Statistical functions

    +

    CAUTION: The functions below will by default include missing values in calculations. Missing values will result in an output of NA, unless the argument na.rm = TRUE is specified. This can be written shorthand as na.rm = T.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ObjectiveFunction
    mean (average)mean(x, na.rm = T)
    medianmedian(x, na.rm= T)
    standard deviationsd(x, na.rm = T)
    quantiles*quantile(x, probs)
    sumsum(x, na.rm = T)
    minimum valuemin(x, na.rm = T)
    maximum valuemax(x, na.rm = T)
    range of numeric valuesrange(x, na.rm = T)
    summary**summary(x)
    +

    Notes:

    +
      +
    • *quantile(): x is the numeric vector to examine, and probs = is a numeric vector with probabilities within 0 and 1.0, e.g c(0.5, 0.8, 0.85).
    • +
    • **summary(): gives a summary on a numeric vector including mean, median, and common percentiles.
    • +
    +

    DANGER: If providing a vector of numbers to one of the above functions, be sure to wrap the numbers within c() .

    +
    +
    # If supplying raw numbers to a function, wrap them in c()
    +mean(1, 6, 12, 10, 5, 0)    # !!! INCORRECT !!!  
    +
    +
    [1] 1
    +
    +
    mean(c(1, 6, 12, 10, 5, 0)) # CORRECT
    +
    +
    [1] 5.666667
    +
    +
    +
    +
    +

    Other useful functions

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ObjectiveFunctionExample
    create a sequenceseq(from, to, by)seq(1, 10, 2)
    repeat x, n timesrep(x, ntimes)rep(1:3, 2) or rep(c("a", "b", "c"), 3)
    subdivide a numeric vectorcut(x, n)cut(linelist$age, 5)
    take a random samplesample(x, size)sample(linelist$id, size = 5, replace = TRUE)
    + +
    +
    +
    +

    %in%

    +

    A very useful operator for matching values, and for quickly assessing if a value is within a vector or data frame.

    +
    +
    my_vector <- c("a", "b", "c", "d")
    +
    +
    +
    "a" %in% my_vector
    +
    +
    [1] TRUE
    +
    +
    "h" %in% my_vector
    +
    +
    [1] FALSE
    +
    +
    +

    To ask if a value is not %in% a vector, put an exclamation mark (!) in front of the logic statement:

    +
    +
    # to negate, put an exclamation in front
    +!"a" %in% my_vector
    +
    +
    [1] FALSE
    +
    +
    !"h" %in% my_vector
    +
    +
    [1] TRUE
    +
    +
    +

    %in% is very useful when using the dplyr function case_when(). You can define a vector previously, and then reference it later. For example:

    +
    +
    affirmative <- c("1", "Yes", "YES", "yes", "y", "Y", "oui", "Oui", "Si")
    +
    +linelist <- linelist %>% 
    +  mutate(child_hospitaled = case_when(
    +    hospitalized %in% affirmative & age < 18 ~ "Hospitalized Child",
    +    TRUE                                      ~ "Not"))
    +
    +

    Note: If you want to detect a partial string, perhaps using str_detect() from stringr, it will not accept a character vector like c("1", "Yes", "yes", "y"). Instead, it must be given a regular expression - one condensed string with OR bars, such as “1|Yes|yes|y”. For example, str_detect(hospitalized, "1|Yes|yes|y"). See the page on Characters and strings for more information.

    +

    You can convert a character vector to a named regular expression with this command:

    +
    +
    affirmative <- c("1", "Yes", "YES", "yes", "y", "Y", "oui", "Oui", "Si")
    +affirmative
    +
    +
    [1] "1"   "Yes" "YES" "yes" "y"   "Y"   "oui" "Oui" "Si" 
    +
    +
    # condense to 
    +affirmative_str_search <- paste0(affirmative, collapse = "|")  # option with base R
    +affirmative_str_search <- str_c(affirmative, collapse = "|")   # option with stringr package
    +
    +affirmative_str_search
    +
    +
    [1] "1|Yes|YES|yes|y|Y|oui|Oui|Si"
    +
    +
    + + + +
    +
    +
    +

    3.13 Errors & warnings

    +

    This section explains:

    +
      +
    • The difference between errors and warnings.
      +
    • +
    • General syntax tips for writing R code.
      +
    • +
    • Code assists.
    • +
    +

    Common errors and warnings and troubleshooting tips can be found in the page on Errors and help.

    + +
    +

    Error versus Warning

    +

    When a command is run, the R Console may show you warning or error messages in red text.

    +
      +
    • A warning means that R has completed your command, but had to take additional steps or produced unusual output that you should be aware of.

    • +
    • An error means that R was not able to complete your command.

    • +
    +

    Look for clues:

    +
      +
    • The error/warning message will often include a line number for the problem.

    • +
    • If an object “is unknown” or “not found”, perhaps you spelled it incorrectly, forgot to call a package with library(), or forgot to re-run your script after making changes.

    • +
    +

    If all else fails, copy the error message into Google along with some key terms - chances are that someone else has worked through this already!

    + +
    +
    +

    General syntax tips

    +

    A few things to remember when writing commands in R, to avoid errors and warnings:

    +
      +
    • Always close parentheses - tip: count the number of opening “(” and closing parentheses “)” for each code chunk.
    • +
    • Avoid spaces in column and object names. Use underscore ( _ ) or periods ( . ) instead.
    • +
    • Keep track of and remember to separate a function’s arguments with commas.
    • +
    • R is case-sensitive, meaning Variable_A is different from variable_A.
    • +
    + +
    +
    +

    Code assists

    +

    Any script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the left hand side of the script, to warn you.

    + + +
    +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/basics.qmd b/new_pages/basics.qmd index ca13857f..eba7c041 100644 --- a/new_pages/basics.qmd +++ b/new_pages/basics.qmd @@ -12,7 +12,7 @@ Parts of this page have been adapted with permission from the [R4Epis project](h See the page on [Transition to R](transition_to_R.qmd) for tips on switching to R from STATA, SAS, or Excel. -```{r, echo=F} +```{r, echo=F, warning=F, message = F} # import the cleaned ebola linelist linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) pacman::p_load(apyramid) @@ -40,13 +40,13 @@ The R community of users is enormous and collaborative. New packages and tools t **RStudio** - RStudio is a Graphical User Interface (GUI) for easier use of **R**. Read more [in the RStudio section](#rstudio). -**Objects** - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are *objects* which are *assigned a name* and *can be referenced* in later commands. Read more [in the Objects section](#objects). +**Objects** - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are *objects* which are *assigned a name* and *can be referenced* in later commands. Read more in the [Objects section](#objects). **Functions** - A function is a code operation that accept inputs and returns a transformed output. Read more [in the Functions section](#functions). **Packages** - An R package is a shareable bundle of functions. Read more [in the Packages section](#packages). -**Scripts** - A script is the document file that hold your commands. Read more [in the Scripts section](#scripts) +**Scripts** - A script is the document file that hold your commands. Read more [in the Scripts section](#scripts). ## Resources for learning {#learning} @@ -70,33 +70,24 @@ The R package [**swirl**](https://swirlstats.com/) offers interactive courses in There are many PDF "cheatsheets" available on the [RStudio website](https://rstudio.com/resources/cheatsheets/), for example: -- Factors with **forcats** package\ -- Dates and times with **lubridate** package\ -- Strings with **stringr** package\ -- iterative opertaions with **purrr** package\ -- Data import\ -- Data transformation cheatsheet with **dplyr** package\ -- R Markdown (to create documents like PDF, Word, Powerpoint...)\ -- Shiny (to build interactive web apps)\ -- Data visualization with **ggplot2** package\ -- Cartography (GIS)\ -- **leaflet** package (interactive maps)\ -- Python with R (**reticulate** package) - -This is an online R resource specifically for [Excel users](https://jules32.github.io/r-for-excel-users/) +- Factors with **forcats** package.\ +- Dates and times with **lubridate** package.\ +- Strings with **stringr** package.\ +- iterative opertaions with **purrr** package.\ +- Data import.\ +- Data transformation cheatsheet with **dplyr** package.\ +- R Markdown (to create documents like PDF, Word, Powerpoint...).\ +- Shiny (to build interactive web apps).\ +- Data visualization with **ggplot2** package.\ +- Cartography (GIS).\ +- **leaflet** package (interactive maps).\ +- Python with R (**reticulate** package). + +This is an online R resource specifically for [Excel users](https://jules32.github.io/r-for-excel-users/). ### Twitter {.unnumbered} -R has a vibrant twitter community where you can learn tips, shortcuts, and news - follow these accounts: - -- Follow us! [\@epiRhandbook](https://twitter.com/epirhandbook)\ -- R Function A Day [\@rfuntionaday](https://twitter.com/rfunctionaday) is an *incredible* resource\ -- R for Data Science [\@rstats4ds](https://twitter.com/rstats4ds?lang=en)\ -- RStudio [\@RStudio](https://twitter.com/rstudio?lang=en)\ -- RStudio Tips [\@rstudiotips](https://twitter.com/rstudiotips)\ -- R-Bloggers [\@Rbloggers](https://twitter.com/Rbloggers)\ -- R-ladies [\@RLadiesGlobal](https://twitter.com/RLadiesGlobal)\ -- Hadley Wickham [\@hadleywickham](https://twitter.com/hadleywickham?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor) +- Follow us! [\@epiRhandbook](https://twitter.com/epirhandbook) Also: @@ -104,13 +95,13 @@ Also: ### Free online resources {.unnumbered} -A definitive text is the [R for Data Science](https://r4ds.had.co.nz/) book by Garrett Grolemund and Hadley Wickham +A definitive text is the [R for Data Science](https://r4ds.hadley.nz/) book by Garrett Grolemund and Hadley Wickham. -The [R4Epis](https://r4epis.netlify.app/) project website aims to "develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting." You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up. +The [R4Epis](https://r4epis.netlify.app/) project website aims to "develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting". You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up. ### Languages other than English {.unnumbered} -[Materiales de RStudio en Español](https://www.rstudio.com/collections/espanol/) +[Materiales de RStudio en Español](https://github.com/cienciadedatos/) [Introduction à R et au tidyverse (Francais)](https://juba.github.io/tidyverse/index.html) @@ -135,9 +126,9 @@ Note that you should install R and RStudio to a drive where you have read and wr Your version of R is printed to the R Console at start-up. You can also run `sessionInfo()`. -To update R, go to the website mentioned above and re-install R. Alternatively, you can use the **installr** package (on Windows) by running `installr::updateR()`. This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the **installr** [documentation](https://www.r-project.org/nosvn/pandoc/installr.html). +To update R, go to the website mentioned above and re-install R. Alternatively, you can use the **installr** package (on Windows) by running `installr::updateR()`. This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the **installr** [documentation](https://talgalili.github.io/installr/). -Be aware that the old R version will still exist in your computer. You can temporarily run an older version (older "installation") of R by clicking "Tools" -\> "Global Options" in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R. +Be aware that the old R version will still exist in your computer. You can temporarily run an older version of R by clicking "Tools" -\> "Global Options" in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R. To update RStudio, you can go to the website above and re-download RStudio. Another option is to click "Help" -\> "Check for Updates" within RStudio, but this may not show the very latest updates. @@ -145,10 +136,10 @@ To see which versions of R, RStudio, or packages were used when this Handbook as ### Other software you *may* need to install {.unnumbered} -- TinyTeX (*for compiling an RMarkdown document to PDF*)\ -- Pandoc (*for compiling RMarkdown documents*)\ -- RTools (*for building packages for R*)\ -- phantomjs (*for saving still images of animated networks, such as transmission chains*) +- TinyTeX (for compiling an RMarkdown document to PDF).\ +- Pandoc (for compiling RMarkdown documents).\ +- RTools (for building packages for R).\ +- phantomjs (for saving still images of animated networks, such as transmission chains). #### TinyTex {.unnumbered} @@ -183,11 +174,13 @@ This is often used to take "screenshots" of webpages. For example when you make ### RStudio orientation {.unnumbered} -**First, open RStudio.** As their icons can look very similar, be sure you are opening *RStudio* and not R. +**First, open RStudio.** + +As their icons can look very similar, be sure you are opening *RStudio* and not R. For RStudio to work you must also have R installed on the computer (see above for installation instructions). -**RStudio** is an interface (GUI) for easier use of **R**. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) [here](https://www.rstudio.com/wp-content/uploads/2016/01/rstudio-IDE-cheatsheet.pdf) +**RStudio** is an interface (GUI) for easier use of **R**. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) [here](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html). By default RStudio displays four rectangle panes. @@ -232,50 +225,53 @@ knitr::include_graphics(here::here("images", "RStudio_tools_options.png")) **Restart** -If your R freezes, you can re-start R by going to the Session menu and clicking "Restart R". This avoids the hassle of closing and opening RStudio. Everything in your R environment will be removed when you do this. +If your R freezes, you can re-start R by going to the Session menu and clicking "Restart R". This avoids the hassle of closing and opening RStudio. + + +**_CAUTION:_** Everything in your R environment will be removed when you do this. ### Keyboard shortcuts {.unnumbered} -Some very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux in the second page of this RStudio [user interface cheatsheet](https://www.rstudio.com/wp-content/uploads/2016/01/rstudio-IDE-cheatsheet.pdf). +Some very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux [Rstudio user interface cheatsheet](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html#keyboard-shortcuts). +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ | Windows/Linux | Mac | Action | +==================================+========================+================================================================================================================================+ -| Esc | Esc | Interrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing "+" in the R console) | +| Esc | Esc | Interrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing "+" in the R console). | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl+s | Cmd+s | Save (script) | +| Ctrl+s | Cmd+s | Save (script). | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Tab | Tab | Auto-complete | +| Tab | Tab | Auto-complete. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Enter | Cmd + Enter | Run current line(s)/selection of code | +| Ctrl + Enter | Cmd + Enter | Run current line(s)/selection of code. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Shift + C | Cmd + Shift + c | comment/uncomment the highlighted lines | +| Ctrl + Shift + C | Cmd + Shift + c | Comment/uncomment the highlighted lines. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Alt + - | Option + - | Insert `<-` | +| Alt + - | Option + - | Insert `<-`. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Shift + m | Cmd + Shift + m | Insert `%>%` | +| Ctrl + Shift + m | Cmd + Shift + m | Insert `%>%`. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + l | Cmd + l | Clear the R console | +| Ctrl + l | Cmd + l | Clear the R console. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Alt + b | Cmd + Option + b | Run from start to current line | +| Ctrl + Alt + b | Cmd + Option + b | Run from start to current. line | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Alt + t | Cmd + Option + t | Run the current code section (R Markdown) | +| Ctrl + Alt + t | Cmd + Option + t | Run the current code section (R Markdown). | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Alt + i | Cmd + Shift + r | Insert code chunk (into R Markdown) | +| Ctrl + Alt + i | Cmd + Shift + r | Insert code chunk (into R Markdown). | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Alt + c | Cmd + Option + c | Run current code chunk (R Markdown) | +| Ctrl + Alt + c | Cmd + Option + c | Run current code chunk (R Markdown). | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| up/down arrows in R console | Same | Toggle through recently run commands | +| up/down arrows in R console | Same | Toggle through recently run commands. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Shift + up/down arrows in script | Same | Select multiple code lines | +| Shift + up/down arrows in script | Same | Select multiple code lines. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + f | Cmd + f | Find and replace in current script | +| Ctrl + f | Cmd + f | Find and replace in current script. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Ctrl + Shift + f | Cmd + Shift + f | Find in files (search/replace across many scripts) | +| Ctrl + Shift + f | Cmd + Shift + f | Find in files (search/replace across many scripts). | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Alt + l | Cmd + Option + l | Fold selected code | +| Alt + l | Cmd + Option + l | Fold selected code. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ -| Shift + Alt + l | Cmd + Shift + Option+l | Unfold selected code | +| Shift + Alt + l | Cmd + Shift + Option+l | Unfold selected code. | +----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ [***TIP:*** Use your Tab key when typing to engage RStudio's auto-complete functionality. This can prevent spelling errors. Press Tab while typing to produce a drop-down menu of likely functions and objects, based on what you have typed so far.]{style="color: darkgreen;"} @@ -288,9 +284,9 @@ Functions are at the core of using R. Functions are how you perform tasks and op This basics section on functions explains: -- What a function is and how they work\ -- What function *arguments* are\ -- How to get help understanding a function +- What a function is and how they work.\ +- What function *arguments* are.\ +- How to get help understanding a function. *A quick note on syntax:* In this handbook, functions are written in code-text with open parentheses, like this: `filter()`. As explained in the [packages](#packages) section, functions are downloaded within *packages*. In this handbook, package names are written in **bold**, like **dplyr**. Sometimes in example code you may see the function name linked explicitly to the name of its package with two colons (`::`) like this: `dplyr::filter()`. The purpose of this linkage is explained in the packages section. @@ -298,7 +294,7 @@ This basics section on functions explains: ### Simple functions {.unnumbered} -**A function is like a machine that receives inputs, does some action with those inputs, and produces an output.** What the output is depends on the function. +**A function is like a machine that receives inputs, carries out an action with those inputs, and produces an output.** What the output is depends on the function. **Functions typically operate upon some object placed within the function's parentheses**. For example, the function `sqrt()` calculates the square root of a number: @@ -321,9 +317,9 @@ summary(linelist$age) Functions often ask for several inputs, called ***arguments***, located within the parentheses of the function, usually separated by commas. -- Some arguments are required for the function to work correctly, others are optional\ -- Optional arguments have default settings\ -- Arguments can take character, numeric, logical (TRUE/FALSE), and other inputs +- Some arguments are required for the function to work correctly, others are optional.\ +- Optional arguments have default settings.\ +- Arguments can take character, numeric, logical (TRUE/FALSE), and other inputs. Here is a fun fictional function, called `oven_bake()`, as an example of a typical function. It takes an input object (e.g. a dataset, or in this example "dough") and performs operations on it as specified by additional arguments (`minutes =` and `temperature =`). The output can be printed to the console, or saved as an object using the assignment operator `<-`. @@ -331,7 +327,7 @@ Here is a fun fictional function, called `oven_bake()`, as an example of a typic knitr::include_graphics(here::here("images", "Function_Bread_Example.png")) ``` -**In a more realistic example**, the `age_pyramid()` command below produces an age pyramid plot based on defined age groups and a binary split column, such as `gender`. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish `linelist` as the dataframe to use, `age_cat5` as the column to count, and `gender` as the binary column to use for splitting the pyramid by color. +**In a more realistic example**, the `age_pyramid()` command below produces an age pyramid plot based on defined age groups and a binary split column, such as `gender`. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish `linelist` as the data frame to use, `age_cat5` as the column to count, and `gender` as the binary column to use for splitting the pyramid by color. ```{r basics_functions_arguments, include=FALSE, results='hide', message=FALSE, warning=FALSE,} ## create an age group variable by specifying categorical breaks @@ -364,7 +360,7 @@ age_pyramid(linelist, "age_cat5", "gender") **A more complex `age_pyramid()` command might include the *optional* arguments to:** - Show proportions instead of counts (set `proportional = TRUE` when the default is `FALSE`)\ -- Specify the two colors to use (`pal =` is short for "palette" and is supplied with a vector of two color names. See the [objects](#objectstructure) page for how the function `c()` makes a vector) +- Specify the two colors to use (`pal =` is short for "palette" and is supplied with a vector of two color names. See the [objects](basics.qmd#objects) page for how the function `c()` makes a vector) [***NOTE:*** For arguments that you specify with both parts of the argument (e.g. `proportional = TRUE`), their order among all the arguments does not matter.]{style="color: black;"} @@ -377,6 +373,7 @@ age_pyramid( pal = c("orange", "purple") # colors ) ``` +***TIP:*** Remember that you can put `?` before a function to see what arguments the function can take, and which arguments are needed and which arguments have default values. For example `?age_pyramid'. @@ -384,9 +381,9 @@ age_pyramid( R is a language that is oriented around functions, so you should feel empowered to write your own functions. Creating functions brings several advantages: -- To facilitate modular programming - the separation of code in to independent and manageable pieces\ -- Replace repetitive copy-and-paste, which can be error prone\ -- Give pieces of code memorable names +- To facilitate modular programming - the separation of code in to independent and manageable pieces.\ +- Replace repetitive copy-and-paste, which can be error prone.\ +- Give pieces of code memorable names. How to write a function is covered in-depth in the [Writing functions](writing_functions.qmd) page. @@ -474,7 +471,7 @@ On installation, R contains **"base"** packages and functions that perform commo *Functions* are contained within **packages** which can be downloaded ("installed") to your computer from the internet. Once a package is downloaded, it is stored in your "library". You can then access the functions it contains during your current R session by "loading" the package. -*Think of R as your personal library*: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow ("load") that book from your library. +*Think of R as your personal library*: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow, "load", that book from your library. In summary: to use the functions available in an R package, 2 steps must be implemented: @@ -483,7 +480,7 @@ In summary: to use the functions available in an R package, 2 steps must be impl #### Your library {.unnumbered} -Your "library" is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called "win-library". For example: `R\win-library\4.0` (the 4.0 is the R version - you'll have a different library for each R version you've downloaded). +Your "library" is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called "library". For example: `R\4.4.1\library` (the 4.4.1 is the R version - you'll have a different library for each R version you've downloaded). You can print the file path to your library by entering `.libPaths()` (empty parentheses). This becomes especially important if working with [R on network drives](network_drives.qmd). @@ -497,14 +494,16 @@ Are you worried about viruses and security when downloading a package from CRAN? In this handbook, we suggest using the **pacman** package (short for "package manager"). It offers a convenient function `p_load()` which will install a package if necessary *and* load it for use in the current R session. -The syntax quite simple. Just list the names of the packages within the `p_load()` parentheses, separated by commas. This command will install the **rio**, **tidyverse**, and **here** packages if they are not yet installed, and will load them for use. This makes the `p_load()` approach convenient and concise if sharing scripts with others. Note that package names are case-sensitive. +The syntax quite simple. Just list the names of the packages within the `p_load()` parentheses, separated by commas. This command will install the **rio**, **tidyverse**, and **here** packages if they are not yet installed, and will load them for use. This makes the `p_load()` approach convenient and concise if sharing scripts with others. + +Note that package names are case-sensitive. ```{r} # Install (if necessary) and load packages for use pacman::p_load(rio, tidyverse, here) ``` -Note that we have used the syntax `pacman::p_load()` which explicitly writes the package name (**pacman**) prior to the function name (`p_load()`), connected by two colons `::`. This syntax is useful because it also loads the **pacman** package (assuming it is already installed). +Here we have used the syntax `pacman::p_load()` which explicitly writes the package name (**pacman**) prior to the function name (`p_load()`), connected by two colons `::`. This syntax is useful because it also loads the **pacman** package (assuming it is already installed). There are alternative **base** R functions that you will see often. The **base** R function for installing a package is `install.packages()`. The name of the package to install must be provided in the parentheses *in quotes*. If you want to install multiple packages in one command, they must be listed within a character vector `c()`. @@ -529,11 +528,11 @@ library(rio) library(here) ``` -To check whether a package in installed and/or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session. +To check whether a package is installed or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session. **Install from Github** -Sometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the *development version* with new features not yet offered in the more stable published CRAN version. These are often hosted on the website [github.com](https://github.com/) in a free, public-facing code "repository". Read more about Github in the handbook page on [Version control and collaboration with Git and Github]. +Sometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the *development version* with new features not yet offered in the more stable published CRAN version. These are often hosted on the website [github.com](https://github.com/) in a free, public-facing code "repository". Read more about Github in the handbook page on [Version control and collaboration with Git and Github](collaboration.qmd). To download R packages from Github, you can use the function `p_load_gh()` from **pacman**, which will install the package if necessary, and load it for use in your current R session. Alternatives to install include using the **remotes** or **devtools** packages. Read more about all the **pacman** functions in the [package documentation](https://cran.r-project.org/web/packages/pacman/pacman.pdf). @@ -541,7 +540,7 @@ To install from Github, you have to provide more information. You must provide: 1) The Github ID of the repository owner 2) The name of the repository that contains the package\ -3) *(optional) The name of the "branch" (specific development version) you want to download* +3) Optional: *The name of the "branch" (specific development version) you want to download* In the examples below, the first word in the quotation marks is the Github ID of the repository owner, after the slash is the name of the repository (the name of the package). @@ -595,7 +594,7 @@ linelist <- rio::import("linelist.xlsx", which = "Sheet1") ### Function help {.unnumbered} -To read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like `?thefunctionname` (put the name of the function after a question mark) and the Help page will appear in the Help pane. Finally, try searching online for resources. +To read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like `?thefunctionname` (for example, to get help for the function `p_load` you would write `?p_load`) and the Help page will appear in the Help pane. Finally, try searching online for resources. ### Update packages {.unnumbered} @@ -603,7 +602,7 @@ You can update packages by re-installing them. You can also click the green "Upd ### Delete packages {.unnumbered} -Use `p_delete()` from **pacman**, or `remove.packages()` from **base** R. Alternatively, go find the folder which contains your library and manually delete the folder. +Use `p_delete()` from **pacman**, or `remove.packages()` from **base** R. ### Dependencies {.unnumbered} @@ -648,16 +647,16 @@ See the page on [Suggested packages](packages_suggested.qmd) for a listing of pa Scripts are a fundamental part of programming. They are documents that hold your commands (e.g. functions to create and modify datasets, print visualizations, etc). You can save a script and run it again later. There are many advantages to storing and running your commands from a script (vs. typing commands one-by-one into the R console "command line"): -- Portability - you can share your work with others by sending them your scripts\ -- Reproducibility - so that you and others know exactly what you did\ -- Version control - so you can track changes made by yourself or colleagues\ -- Commenting/annotation - to explain to your colleagues what you have done +- Portability - you can share your work with others by sending them your scripts.\ +- Reproducibility - so that you and others know exactly what you did.\ +- Version control - so you can track changes made by yourself or colleagues.\ +- Commenting/annotation - to explain to your colleagues what you have done. ### Commenting {.unnumbered} In a script you can also annotate ("comment") around your R code. Commenting is helpful to explain to yourself and other readers what you are doing. You can add a comment by typing the hash symbol (\#) and writing your comment after it. The commented text will appear in a different color than the R code. -Any code written after the \# will not be run. Therefore, placing a \# before code is also a useful way to temporarily block a line of code ("comment out") if you do not want to delete it). You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac). +Any code written after the \# will not be run. Therefore, placing a \# before code is also a useful way to temporarily block a line of code ("comment out") if you do not want to delete it. You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac). ```{r, eval = F} # A comment can be on a line by itself @@ -668,9 +667,10 @@ linelist <- import("linelist_raw.xlsx") %>% # a comment can also come after co ``` -- Comment on *what* you are doing *and on **why** you are doing it*.\ -- Break your code into logical sections\ -- Accompany your code with a text step-by-step description of what you are doing (e.g. numbered steps) +There are a few general ideas to follow when writing your scripts in order to make them accessible. +- Add comments on *what* you are doing *and on **why** you are doing it*.\ +- Break your code into logical sections.\ +- Accompany your code with a text step-by-step description of what you are doing (e.g. numbered steps). ### Style {.unnumbered} @@ -690,9 +690,11 @@ knitr::include_graphics(here::here("images", "example_script.png")) -### R markdown {.unnumbered} +### R markdown and Quarto {.unnumbered} + +An [R Markdown](https://rmarkdown.rstudio.com/) or [Quarto](https://quarto.org/docs/computations/r.html) script are types of R script in which the script itself *becomes* an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports. -An R markdown script is a type of R script in which the script itself *becomes* an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports. Even this website and handbook is produced with R markdown scripts! +Even this website and handbook is produced with Quarto scripts! It is worth noting that beginner R users can also use R Markdown - do not be intimidated! To learn more, see the handbook page on [Reports with R Markdown](rmarkdown.qmd) documents. @@ -708,13 +710,13 @@ There is no difference between writing in a Rmarkdown vs an R notebook. However Shiny apps/websites are contained within one script, which must be named `app.R`. This file has three components: -1) A user interface (ui)\ -2) A server function\ -3) A call to the `shinyApp` function +1) A user interface (ui).\ +2) A server function.\ +3) A call to the `shinyApp` function. See the handbook page on [Dashboards with Shiny](shiny_basics.qmd), or this online tutorial: [Shiny tutorial](https://shiny.rstudio.com/tutorial/written-tutorial/lesson1/) -*In older times, the above file was split into two files (`ui.R` and `server.R`)* +*In previous versions, the above file was split into two files (`ui.R` and `server.R`)* ### Code folding {.unnumbered} @@ -724,7 +726,7 @@ To do this, create a text header with \#, write your header, and follow it with To expand the code, either click the arrow in the gutter again, or the dual-arrow icon. There are also keyboard shortcuts as explained in the [RStudio section](#rstudio) of this page. -By creating headers with \#, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more \# symbols, for example \# for primary, \#\# for seconary, and \#\#\# for tertiary headers. +By creating headers with \#, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more \# symbols, for example \# for primary, \#\# for secondary, and \#\#\# for tertiary headers. Below are two versions of an example script. On the left is the original with commented headers. On the right, four dashes have been written after each header, making them collapsible. Two of them have been collapsed, and you can see that the Table of Contents at the bottom now shows each section. @@ -754,6 +756,7 @@ knitr::include_graphics(here::here("images", "working_directory_1.png")) ### Recommended approach {.unnumbered} **See the page on [R projects](r_projects.qmd) for details on our recommended approach to managing your working directory.**\ + A common, efficient, and trouble-free way to manage your working directory and file paths is to combine these 3 elements in an [R project](r_projects.qmd)-oriented workflow: 1) An R Project to store all your files (see page on [R projects](r_projects.qmd))\ @@ -764,7 +767,11 @@ A common, efficient, and trouble-free way to manage your working directory and f ### Set by command {.unnumbered} -Until recently, many people learning R were taught to begin their scripts with a `setwd()` command. Please instead consider using an [R project][r_projects.qmd]-oriented workflow and read the [reasons for not using `setwd()`](https://www.tidyverse.org/blog/2017/12/workflow-vs-script/). In brief, your work becomes specific to your computer, file paths used to import and export files become "brittle", and this severely hinders collaboration and use of your code on any other computer. There are easy alternatives! +Until recently, many people learning R were taught to begin their scripts with a `setwd()` command. + +Please instead consider using an [R project](r_projects.qmd)-oriented workflow and read the [reasons for not using `setwd()`](https://www.tidyverse.org/blog/2017/12/workflow-vs-script/). + +In brief, your work becomes specific to your computer. This means that file paths used to import and export files need to be changed if used on a different computer, or by different collaborators. As noted above, although we do not recommend this approach in most circumstances, you can use the command `setwd()` with the desired folder file path in quotations, for example: @@ -772,7 +779,7 @@ As noted above, although we do not recommend this approach in most circumstances setwd("C:/Documents/R Files/My analysis") ``` -[***DANGER:*** Setting a working directory with `setwd()` *can* be "brittle" if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory (with the **here** package). ]{style="color: red;"} +[***DANGER:*** Setting a working directory with `setwd()` *can* be "brittle" if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory, such as with the [**here** package]. ]{style="color: red;"} @@ -814,7 +821,9 @@ Below is an example of an "absolute" or "full address" file path. These will lik **Slash direction** -*If typing in a file path, be aware the direction of the slashes.* Use *forward slashes* (`/`) to separate the components ("data/provincial.csv"). For Windows users, the default way that file paths are displayed is with *back slashes* (\\) - so you will need to change the direction of each slash. If you use the **here** package as described in the [R projects](r_projects.qmd) page the slash direction is not an issue. +*If typing in a file path, be aware the direction of the slashes.* + +Use *forward slashes* (`/`) to separate the components ("data/provincial.csv"). For Windows users, the default way that file paths are displayed is with *back slashes* (\\) - so you will need to change the direction of each slash. If you use the **here** package as described in the [R projects](r_projects.qmd) page the slash direction is not an issue. **Relative file paths** @@ -825,7 +834,7 @@ We generally recommend providing "relative" filepaths instead - that is, the pat linelist <- import(here("data", "clean", "linelists", "marin_country.csv")) ``` -Even if using relative file paths within an R project, you can still use absolute paths to import/export data outside your R project. +Even if using relative file paths within an R project, you can still use absolute paths to import and export data outside your R project. @@ -833,16 +842,17 @@ Even if using relative file paths within an R project, you can still use absolut Everything in R is an object, and R is an "object-oriented" language. These sections will explain: -- How to create objects (`<-`) -- Types of objects (e.g. data frames, vectors..)\ -- How to access subparts of objects (e.g. variables in a dataset)\ -- Classes of objects (e.g. numeric, logical, integer, double, character, factor) +- How to create objects (`<-`). +- Types of objects (e.g. data frames, vectors..).\ +- How to access subparts of objects (e.g. variables in a dataset).\ +- Classes of objects (e.g. numeric, logical, integer, double, character, factor). ### Everything is an object {.unnumbered} *This section is adapted from the [R4Epis project](https://r4epis.netlify.app/training/r_basics/objects/).*\ + Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are **objects** which are **assigned a name** and **can be referenced** in later commands. An object exists when you have assigned it a value (see the assignment section below). When it is assigned a value, the object appears in the Environment (see the upper right pane of RStudio). It can then be operated upon, manipulated, changed, and re-defined. @@ -886,9 +896,9 @@ You will also see equals signs in R code: **Datasets** -Datasets are also objects (typically "dataframes") and must be assigned names when they are imported. In the code below, the object `linelist` is created and assigned the value of a CSV file imported with the **rio** package and its `import()` function. +Datasets are also objects (typically "data frames") and must be assigned names when they are imported. In the code below, the object `linelist` is created and assigned the value of a CSV file imported with the **rio** package and its `import()` function. -```{r basics_objects_dataframes, eval=FALSE} +```{r basics_objects_data frames, eval=FALSE} # linelist is created and assigned the value of the imported CSV file linelist <- import("my_linelist.csv") ``` @@ -899,7 +909,7 @@ You can read more about importing and exporting datasets with the section on [Im - Object names must not contain spaces, but you should use underscore (\_) or a period (.) instead of a space.\ - Object names are case-sensitive (meaning that Dataset_A is different from dataset_A). -- Object names must begin with a letter (cannot begin with a number like 1, 2 or 3). +- Object names must begin with a letter (they cannot begin with a number like 1, 2 or 3). **Outputs** @@ -922,7 +932,7 @@ gen_out_table **Columns** -Columns in a dataset are also objects and can be defined, over-written, and created as described below in the section on Columns. +Columns in a dataset are also objects and can be defined, over-written, and created as described below in the section on [Columns](basics.qmd#columnsvariables). You can use the assignment operator from **base** R to create a new column. Below, the new column `bmi` (Body Mass Index) is created, and for each row the new value is result of a mathematical operation on the row's value in the `wt_kg` and `ht_cm` columns. @@ -955,7 +965,8 @@ In epidemiology (and particularly field epidemiology), you will *most commonly* +------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ | Common structure | Explanation | Example | -+==================+==================================================================================================+=====================================================================================+ ++------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ + | Vectors | A container for a sequence of singular objects, all of the same class (e.g. numeric, character). | **"Variables" (columns) in data frames are vectors** (e.g. the column `age_years`). | +------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ | Data Frames | Vectors (e.g. columns) that are bound together that all have the same number of rows. | `linelist` is a data frame. | @@ -980,7 +991,7 @@ All the objects stored in R have a *class* which tells R how to handle the objec +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | Factor | These are vectors that have a **specified order** or hierarchy of values | An variable of economic status with ordered values | +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ -| Date | **Once R is told that certain data are Dates**, these data can be manipulated and displayed in special ways. See the page on [Working with dates](dates.qmd) for more information. | 2018-04-12 or 15/3/1954 or Wed 4 Jan 1980 | +| Date | **Once R is told that certain data are Dates**, these data can be manipulated and displayed in special ways. See the page on [Working with dates](dates.qmd) for more information. | 2018-04-12 or 15/3/1954 or Wed 4 Jan 1980 +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | Logical | Values must be one of the two special values TRUE or FALSE (note these are **not** "TRUE" and "FALSE" in quotation marks) | TRUE or FALSE | +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ @@ -988,7 +999,7 @@ All the objects stored in R have a *class* which tells R how to handle the objec +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ | tibble | tibbles are a variation on data frame, the main operational difference being that they print more nicely to the console (display first 10 rows and only columns that fit on the screen) | Any data frame, list, or matrix can be converted to a tibble with `as_tibble()` | +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ -| list | A list is like vector, but holds other objects that can be other different classes | A list could hold a single number, and a dataframe, and a vector, and even another list within it! | +| list | A list is like vector, but holds other objects that can be other different classes | A list could hold a single number, and a data frame, and a vector, and even another list within it! | +------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ **You can test the class of an object by providing its name to the function `class()`**. Note: you can reference a specific column within a dataset using the `$` notation to separate the name of the dataset and the name of the column. @@ -1004,7 +1015,7 @@ class(linelist$gender) # class should be character Sometimes, a column will be converted to a different class automatically by R. Watch out for this! For example, if you have a vector or column of numbers, but a character value is inserted... the entire column will change to class character. ```{r} -num_vector <- c(1,2,3,4,5) # define vector as all numbers +num_vector <- c(1, 2, 3, 4, 5) # define vector as all numbers class(num_vector) # vector is numeric class num_vector[3] <- "three" # convert the third element to a character class(num_vector) # vector is now character class @@ -1021,14 +1032,14 @@ One common example of this is when manipulating a data frame in order to print a +------------------+---------------------------------------------------------------------------------------+ | `as.integer()` | Converts to integer class | +------------------+---------------------------------------------------------------------------------------+ -| `as.Date()` | Converts to Date class - Note: see section on [dates](dates.qmd) for details | +| `as.Date()` | Converts to Date class - Note: see section on [dates](dates.qmd) for details | +------------------+---------------------------------------------------------------------------------------+ | `factor()` | Converts to factor - Note: re-defining order of value levels requires extra arguments | +------------------+---------------------------------------------------------------------------------------+ Likewise, there are **base** R functions to check whether an object IS of a specific class, such as `is.numeric()`, `is.character()`, `is.double()`, `is.factor()`, `is.integer()` -Here is [more online material on classes and data structures in R](https://swcarpentry.github.io/r-novice-inflammation/13-supp-data-structures/). +Here is [more online material on classes and data structures in R](https://swcarpentry.github.io/r-novice-inflammation/13-supp-data-structures.html). @@ -1054,7 +1065,7 @@ length(linelist$age) # (age is a column in the linelist data frame) ``` -By typing the name of the dataframe followed by `$` you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes! +By typing the name of the data frame followed by `$` you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes! ```{r echo=F, out.width = "100%", fig.align = "center"} knitr::include_graphics(here::here("images", "Calling_Names.gif")) @@ -1066,7 +1077,7 @@ knitr::include_graphics(here::here("images", "Calling_Names.gif")) ### Access/index with brackets (`[ ]`) {.unnumbered} -You may need to view parts of objects, also called "indexing", which is often done using the square brackets `[ ]`. Using `$` on a dataframe to access a column is also a type of indexing. +You may need to view parts of objects, also called "indexing", which is often done using the square brackets `[ ]`. Using `$` on a data frame to access a column is also a type of indexing. ```{r} my_vector <- c("a", "b", "c", "d", "e", "f") # define the vector @@ -1090,7 +1101,7 @@ summary(linelist$age)[["Median"]] ``` -Brackets also work on data frames to view specific rows and columns. You can do this using the syntax `dataframe[rows, columns]`: +Brackets also work on data frames to view specific rows and columns. You can do this using the syntax `data frame[rows, columns]`: ```{r basics_objects_access, eval=F} # View a specific row (2) from dataset, with all columns (don't forget the comma!) @@ -1109,7 +1120,7 @@ linelist[2, c(5:10, 18)] linelist[2:20, c("date_onset", "outcome", "age")] # View rows and columns based on criteria -# *** Note the dataframe must still be named in the criteria! +# *** Note the data frame must still be named in the criteria! linelist[linelist$age > 25 , c("date_onset", "outcome", "age")] # Use View() to see the outputs in the RStudio Viewer pane (easier to read) @@ -1122,7 +1133,7 @@ new_table <- linelist[2:20, c("date_onset")] Note that you can also achieve the above row/column indexing on data frames and tibbles using **dplyr** syntax (functions `filter()` for rows, and `select()` for columns). Read more about these core functions in the [Cleaning data and core functions](cleaning.qmd) page. -To filter based on "row number", you can use the **dplyr** function `row_number()` with open parentheses as part of a logical filtering statement. Often you will use the `%in%` operator and a range of numbers as part of that logical statement, as shown below. To see the *first* N rows, you can also use the special **dplyr** function `head()`. +To filter based on "row number", you can use the **dplyr** function `row_number()` with open parentheses as part of a logical filtering statement. Often you will use the `%in%` operator and a range of numbers as part of that logical statement, as shown below. To see the *first* N rows, you can also use the special **dplyr** function `head()`. Note, there is a function `head()` from **base** R, but this is overwritten by the **dplyr** function when you load **tidyverse**. ```{r, eval=F} # View first 100 rows @@ -1132,11 +1143,12 @@ linelist %>% head(100) linelist %>% filter(row_number() == 5) # View rows 2 through 20, and three specific columns (note no quotes necessary on column names) -linelist %>% filter(row_number() %in% 2:20) %>% select(date_onset, outcome, age) +linelist %>% + filter(row_number() %in% 2:20) %>% + select(date_onset, outcome, age) ``` -When indexing an object of class **list**, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list.\ -Brackets can also be written after one another, as demonstrated below. +When indexing an object of class **list**, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list. Brackets can also be written after one another, as demonstrated below. This [visual explanation of lists indexing, with pepper shakers](https://r4ds.had.co.nz/vectors.html#lists-of-condiments) is humorous and helpful. @@ -1204,22 +1216,29 @@ rm(list = ls(all = TRUE)) **Two general approaches to working with objects are:** -1) **Pipes/tidyverse** - pipes send an object from function to function - emphasis is on the *action*, not the object\ -2) **Define intermediate objects** - an object is re-defined again and again - emphasis is on the object +1) **Pipes/tidyverse** - pipes send an object from function to function - emphasis is on the *action*, not the object.\ +2) **Define intermediate objects** - an object is re-defined again and again - emphasis is on the object. ### **Pipes** {.unnumbered} -**Simply explained, the pipe operator (`%>%`) passes an intermediate output from one function to the next.**\ -You can think of it as saying "then". Many functions can be linked together with `%>%`. +**Simply explained, the pipe operator passes an intermediate output from one function to the next.**\ +You can think of it as saying "and then". Many functions can be linked together with `%>%`. + +- **Piping emphasizes a sequence of actions, not the object the actions are being performed on**.\ +- Pipes are best when a sequence of actions must be performed on one object.\ +- Pipes can make code more clean and easier to read, more intuitive. -- **Piping emphasizes a sequence of actions, not the object the actions are being performed on**\ -- Pipes are best when a sequence of actions must be performed on one object\ -- Pipes come from the package **magrittr**, which is automatically included in packages **dplyr** and **tidyverse** -- Pipes can make code more clean and easier to read, more intuitive +Pipe operators were first introduced through the [magrittr package](https://magrittr.tidyverse.org/), which is part of tidyverse, and were specified as `%>%`. In R 4.1.0, they introduced a **base** R pipe which is specified through `|>`. The behaviour of the two pipes is the same, and they can be used somewhat interchangeably. However, there are a few key differences. -Read more on this approach in the tidyverse [style guide](https://style.tidyverse.org/pipes.html) +* The `%>%` pipe allows you to pass multiple arguments. +* The `%>%` pipe lets you drop parentheses when calling a function with no other arguments (i.e. `drop` vs `drop()`). +* The `%>%` pipe allows you to start a pipe with `.` to create a function in your linking of code. + +For these reasons, we recommend the **magrittr** pipe, `%>%`, over the **base** R pipe, `|>`. + +To read more about the differences between base R and tidyverse (magrittr) pipes, see this [blog post](https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/). For more information on the tidyverse approach, please see this [style guide](https://style.tidyverse.org/pipes.html). Here is a fake example for comparison, using fictional functions to "bake a cake". First, the pipe method: @@ -1239,10 +1258,6 @@ cake <- flour %>% # to define cake, start with flour, and then... let_cool() # let it cool down ``` -Here is another [link](https://cfss.uchicago.edu/notes/pipes/#:~:text=Pipes%20are%20an%20extremely%20useful,code%20and%20combine%20multiple%20operations) describing the utility of pipes. - -Piping is not a **base** function. To use piping, the **magrittr** package must be installed and loaded (this is typically done by loading **tidyverse** or **dplyr** package which include it). You can [read more about piping in the magrittr documentation](https://magrittr.tidyverse.org/). - Note that just like other R commands, pipes can be used to just display the result, or to save/re-save an object, depending on whether the assignment operator `<-` is involved. See both below: ```{r, eval=F} @@ -1271,7 +1286,7 @@ linelist %<>% filter(age > 50) ### Define intermediate objects {.unnumbered} -This approach to changing objects/dataframes may be better if: +This approach to changing objects/data frames may be better if: - You need to manipulate multiple objects\ - There are intermediate steps that are meaningful and deserve separate object names @@ -1279,8 +1294,8 @@ This approach to changing objects/dataframes may be better if: **Risks:** - Creating new objects for each step means creating lots of objects. If you use the wrong one you might not realize it!\ -- Naming all the objects can be confusing\ -- Errors may not be easily detectable +- Naming all the objects can be confusing.\ +- Errors may not be easily detectable. Either name each intermediate object, or overwrite the original, or combine all the functions together. All come with their own risks. @@ -1312,12 +1327,12 @@ cake <- let_cool(bake(mix_together(batter_3, utensil = spoon, minutes = 2), degr This section details operators in R, such as: -- Definitional operators\ -- Relational operators (less than, equal too..)\ -- Logical operators (and, or...)\ -- Handling missing values\ -- Mathematical operators and functions (+/-, \>, sum(), median(), ...)\ -- The `%in%` operator +- Definitional operators.\ +- Relational operators (less than, equal too..).\ +- Logical operators (and, or...).\ +- Handling missing values.\ +- Mathematical operators and functions (+/-, \>, sum(), median(), ...).\ +- The `%in%` operator. @@ -1326,8 +1341,7 @@ This section details operators in R, such as: **`<-`** The basic assignment operator in R is `<-`. Such that `object_name <- value`.\ -This assignment operator can also be written as `=`. We advise use of `<-` for general R use.\ -We also advise surrounding such operators with spaces, for readability. +This assignment operator can also be written as `=`. We advise use of `<-` for general R use. We also advise surrounding such operators with spaces, for readability. **`<<-`** @@ -1373,12 +1387,12 @@ Relational operators compare values and are often used when defining new variabl +--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | Less than or equal to | `<=` | `6 <= 4` | `FALSE` | +--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Value is missing | `is.na()` | `is.na(7)` | `FALSE` (see page on [Missing data](missing_data.qmd)) | +| Value is missing | `is.na()` | `is.na(7)` | `FALSE` (see page on [Missing data](missing_data.qmd)) | +--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ | Value is not missing | `!is.na()` | `!is.na(7)` | `TRUE` | +--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ -Logical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses ( ) for grouping and order of application. +Logical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses `( )` for grouping and order of application. +---------------------+-----------------------------------------------------------------------+ | Meaning | Operator | @@ -1414,13 +1428,13 @@ linelist_cleaned <- linelist %>% | If one of the above criteria are not met | "Suspected" | +------------------------------------------------------------------------------------------------+--------------------------------------------+ -*Note that R is case-sensitive, so "Positive" is different than "positive"...* +*Note that R is case-sensitive, so "Positive" is different than "positive".* ### Missing values {.unnumbered} -In R, missing values are represented by the special value `NA` (a "reserved" value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, "Missing", or .), you may want to re-code those values to `NA`. How to do this is addressed in the [Import and export](importing.qmd) page. +In R, missing values are represented by the special value `NA` (a "reserved" value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, "Missing"), you may want to re-code those values to `NA`. How to do this is addressed in the [Import and export](importing.qmd) page. **To test whether a value is `NA`, use the special function `is.na()`**, which returns `TRUE` or `FALSE`. @@ -1477,7 +1491,7 @@ If it is set to a low number (e.g. 0) it will be "turned on" always. To "turn of ```{r, eval=F} # turn off scientific notation -options(scipen=999) +options(scipen = 999) ``` #### Rounding {.unnumbered} @@ -1489,28 +1503,35 @@ options(scipen=999) round(c(2.5, 3.5)) janitor::round_half_up(c(2.5, 3.5)) + +``` + +For rounding from proportion to percentages, you can use the function `percent()` from the **scales** package. + +```{r} +scales::percent(c(0.25, 0.35), accuracy = 0.1) ``` #### Statistical functions {.unnumbered} [***CAUTION:*** The functions below will by default include missing values in calculations. Missing values will result in an output of `NA`, unless the argument `na.rm = TRUE` is specified. This can be written shorthand as `na.rm = T`.]{style="color: orange;"} -| Objective | Function | -|-------------------------|--------------------| -| mean (average) | mean(x, na.rm=T) | -| median | median(x, na.rm=T) | -| standard deviation | sd(x, na.rm=T) | -| quantiles\* | quantile(x, probs) | -| sum | sum(x, na.rm=T) | -| minimum value | min(x, na.rm=T) | -| maximum value | max(x, na.rm=T) | -| range of numeric values | range(x, na.rm=T) | -| summary\*\* | summary(x) | +| Objective | Function | +|-------------------------|----------------------| +| mean (average) | mean(x, na.rm = T) | +| median | median(x, na.rm= T) | +| standard deviation | sd(x, na.rm = T) | +| quantiles\* | quantile(x, probs) | +| sum | sum(x, na.rm = T) | +| minimum value | min(x, na.rm = T) | +| maximum value | max(x, na.rm = T) | +| range of numeric values | range(x, na.rm = T) | +| summary\*\* | summary(x) | Notes: -- `*quantile()`: `x` is the numeric vector to examine, and `probs =` is a numeric vector with probabilities within 0 and 1.0, e.g `c(0.5, 0.8, 0.85)` -- `**summary()`: gives a summary on a numeric vector including mean, median, and common percentiles +- `*quantile()`: `x` is the numeric vector to examine, and `probs =` is a numeric vector with probabilities within 0 and 1.0, e.g `c(0.5, 0.8, 0.85)`. +- `**summary()`: gives a summary on a numeric vector including mean, median, and common percentiles. [***DANGER:*** If providing a vector of numbers to one of the above functions, be sure to wrap the numbers within `c()` .]{style="color: red;"} @@ -1539,7 +1560,7 @@ mean(c(1, 6, 12, 10, 5, 0)) # CORRECT ### `%in%` {.unnumbered} -A very useful operator for matching values, and for quickly assessing if a value is within a vector or dataframe. +A very useful operator for matching values, and for quickly assessing if a value is within a vector or data frame. ```{r} my_vector <- c("a", "b", "c", "d") @@ -1594,9 +1615,9 @@ affirmative_str_search This section explains: -- The difference between errors and warnings\ -- General syntax tips for writing R code\ -- Code assists +- The difference between errors and warnings.\ +- General syntax tips for writing R code.\ +- Code assists. Common errors and warnings and troubleshooting tips can be found in the page on [Errors and help](errors.qmd). @@ -1624,13 +1645,13 @@ If all else fails, copy the error message into Google along with some key terms A few things to remember when writing commands in R, to avoid errors and warnings: -- Always close parentheses - tip: count the number of opening "(" and closing parentheses ")" for each code chunk -- Avoid spaces in column and object names. Use underscore ( \_ ) or periods ( . ) instead -- Keep track of and remember to separate a function's arguments with commas -- R is case-sensitive, meaning `Variable_A` is *different* from `variable_A` +- Always close parentheses - tip: count the number of opening "(" and closing parentheses ")" for each code chunk. +- Avoid spaces in column and object names. Use underscore ( \_ ) or periods ( . ) instead. +- Keep track of and remember to separate a function's arguments with commas. +- R is case-sensitive, meaning `Variable_A` is *different* from `variable_A`. ### Code assists {.unnumbered} -Any script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the right side of the script, to warn you. +Any script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the left hand side of the script, to warn you. diff --git a/new_pages/basics.rmarkdown b/new_pages/basics.rmarkdown new file mode 100644 index 00000000..495eef44 --- /dev/null +++ b/new_pages/basics.rmarkdown @@ -0,0 +1,1791 @@ +# R Basics {#rbasics} + + +```{r out.width = "100%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "basics_header_close.png")) +``` + + +Welcome! + +This page reviews the essentials of R. It is not intended to be a comprehensive tutorial, but it provides the basics and can be useful for refreshing your memory. The section on [Resources for learning](#learning) links to more comprehensive tutorials. + +Parts of this page have been adapted with permission from the [R4Epis project](https://r4epis.netlify.app/). + +See the page on [Transition to R](transition_to_R.qmd) for tips on switching to R from STATA, SAS, or Excel. + + +```{r, echo=F} +# import the cleaned ebola linelist +linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) +pacman::p_load(apyramid) +``` + + + + +## Why use R? + +As stated on the [R project website](https://www.r-project.org/about.html), R is a programming language and environment for statistical computing and graphics. It is highly versatile, extendable, and community-driven. + +**Cost** + +R is free to use! There is a strong ethic in the community of free and open-source material. + +**Reproducibility** + +Conducting your data management and analysis through a programming language (compared to Excel or another primarily point-click/manual tool) enhances **reproducibility**, makes **error-detection** easier, and eases your workload. + +**Community** + +The R community of users is enormous and collaborative. New packages and tools to address real-life problems are developed daily, and vetted by the community of users. As one example, [R-Ladies](https://rladies.org/) is a worldwide organization whose mission is to promote gender diversity in the R community, and is one of the largest organizations of R users. It likely has a chapter near you! + +## Key terms + +**RStudio** - RStudio is a Graphical User Interface (GUI) for easier use of **R**. Read more [in the RStudio section](#rstudio). + +**Objects** - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are *objects* which are *assigned a name* and *can be referenced* in later commands. Read more in the [Objects section](#objects). + +**Functions** - A function is a code operation that accept inputs and returns a transformed output. Read more [in the Functions section](#functions). + +**Packages** - An R package is a shareable bundle of functions. Read more [in the Packages section](#packages). + +**Scripts** - A script is the document file that hold your commands. Read more [in the Scripts section](#scripts) + +## Resources for learning {#learning} + +### Resources within RStudio {.unnumbered} + +**Help documentation** + +Search the RStudio "Help" tab for documentation on R packages and specific functions. This is within the pane that also contains Files, Plots, and Packages (typically in the lower-right pane). As a shortcut, you can also type the name of a package or function into the R console after a question-mark to open the relevant Help page. Do not include parentheses. + +For example: `?filter` or `?diagrammeR`. + +**Interactive tutorials** + +There are several ways to learn R interactively *within* RStudio. + +RStudio itself offers a Tutorial pane that is powered by the [**learnr**](https://blog.rstudio.com/2020/02/25/rstudio-1-3-integrated-tutorials/) R package. Simply install this package and open a tutorial via the new "Tutorial" tab in the upper-right RStudio pane (which also contains Environment and History tabs). + +The R package [**swirl**](https://swirlstats.com/) offers interactive courses in the R Console. Install and load this package, then run the command `swirl()` (empty parentheses) in the R console. You will see prompts appear in the Console. Respond by typing in the Console. It will guide you through a course of your choice. + +### Cheatsheets {.unnumbered} + +There are many PDF "cheatsheets" available on the [RStudio website](https://rstudio.com/resources/cheatsheets/), for example: + +- Factors with **forcats** package.\ +- Dates and times with **lubridate** package.\ +- Strings with **stringr** package.\ +- iterative opertaions with **purrr** package.\ +- Data import.\ +- Data transformation cheatsheet with **dplyr** package.\ +- R Markdown (to create documents like PDF, Word, Powerpoint...).\ +- Shiny (to build interactive web apps).\ +- Data visualization with **ggplot2** package.\ +- Cartography (GIS).\ +- **leaflet** package (interactive maps).\ +- Python with R (**reticulate** package). + +This is an online R resource specifically for [Excel users](https://jules32.github.io/r-for-excel-users/). + +### Twitter {.unnumbered} + +- Follow us! [\@epiRhandbook](https://twitter.com/epirhandbook) + +Also: + +**\#epitwitter** and **\#rstats** + +### Free online resources {.unnumbered} + +A definitive text is the [R for Data Science](https://r4ds.hadley.nz/) book by Garrett Grolemund and Hadley Wickham. + +The [R4Epis](https://r4epis.netlify.app/) project website aims to "develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting". You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up. + +### Languages other than English {.unnumbered} + +[Materiales de RStudio en Español](https://www.rstudio.com/collections/espanol/) + +[Introduction à R et au tidyverse (Francais)](https://juba.github.io/tidyverse/index.html) + + + +## Installation + +### R and RStudio {.unnumbered} + +**How to install R** + +Visit this website and download the latest version of R suitable for your computer. + +**How to install RStudio** + +Visit this website and download the latest free Desktop version of RStudio suitable for your computer. + +**Permissions**\ +Note that you should install R and RStudio to a drive where you have read and write permissions. Otherwise, your ability to install R packages (a frequent occurrence) will be impacted. If you encounter problems, try opening RStudio by right-clicking the icon and selecting "Run as administrator". Other tips can be found in the page [R on network drives](network_drives.qmd). + +**How to update R and RStudio** + +Your version of R is printed to the R Console at start-up. You can also run `sessionInfo()`. + +To update R, go to the website mentioned above and re-install R. Alternatively, you can use the **installr** package (on Windows) by running `installr::updateR()`. This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the **installr** [documentation](https://talgalili.github.io/installr/). + +Be aware that the old R version will still exist in your computer. You can temporarily run an older version of R by clicking "Tools" -\> "Global Options" in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R. + +To update RStudio, you can go to the website above and re-download RStudio. Another option is to click "Help" -\> "Check for Updates" within RStudio, but this may not show the very latest updates. + +To see which versions of R, RStudio, or packages were used when this Handbook as made, see the page on [Editorial and technical notes](editorial_style.qmd). + +### Other software you *may* need to install {.unnumbered} + +- TinyTeX (for compiling an RMarkdown document to PDF).\ +- Pandoc (for compiling RMarkdown documents).\ +- RTools (for building packages for R).\ +- phantomjs (for saving still images of animated networks, such as transmission chains). + +#### TinyTex {.unnumbered} + +TinyTex is a custom LaTeX distribution, useful when trying to produce PDFs from R.\ +See for more informaton. + +To install TinyTex from R: + + +```{r, eval=F} +install.packages('tinytex') +tinytex::install_tinytex() +# to uninstall TinyTeX, run tinytex::uninstall_tinytex() +``` + + +#### Pandoc {.unnumbered} + +Pandoc is a document converter, a separate software from R. **It comes bundled with RStudio and should not need to be downloaded.** It helps the process of converting Rmarkdown documents to formats like .pdf and adding complex functionality. + +#### RTools {.unnumbered} + +RTools is a collection of software for building packages for R + +Install from this website: + +#### phantomjs {.unnumbered} + +This is often used to take "screenshots" of webpages. For example when you make a transmission chain with **epicontacts** package, an HTML file is produced that is interactive and dynamic. If you want a static image, it can be useful to use the [**webshot**](https://wch.github.io/webshot/articles/intro.html) package to automate this process. This will require the external program "phantomjs". You can install phantomjs via the **webshot** package with the command `webshot::install_phantomjs()`. + + + +## RStudio {#rstudio} + +### RStudio orientation {.unnumbered} + +**First, open RStudio.** + +As their icons can look very similar, be sure you are opening *RStudio* and not R. + +For RStudio to work you must also have R installed on the computer (see above for installation instructions). + +**RStudio** is an interface (GUI) for easier use of **R**. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) [here](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html). + +By default RStudio displays four rectangle panes. + + +```{r out.width = "100%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "RStudio_overview.png")) +``` + + +[***TIP:*** If your RStudio displays only one left pane it is because you have no scripts open yet.]{style="color: black;"} + +**The Source Pane**\ +This pane, by default in the upper-left, is a space to edit, run, and save your [scripts](#scripts). Scripts contain the commands you want to run. This pane can also display datasets (data frames) for viewing. + +For Stata users, this pane is similar to your Do-file and Data Editor windows. + +**The R Console Pane** + +The R Console, by default the left or lower-left pane in R Studio, is the home of the R "engine". This is where the commands are actually run and non-graphic outputs and error/warning messages appear. You can directly enter and run commands in the R Console, but realize that these commands are not saved as they are when running commands from a script. + +If you are familiar with Stata, the R Console is like the Command Window and also the Results Window. + +**The Environment Pane**\ +This pane, by default in the upper-right, is most often used to see brief summaries of [objects](#objects) in the R Environment in the current session. These objects could include imported, modified, or created datasets, parameters you have defined (e.g. a specific epi week for the analysis), or vectors or lists you have defined during analysis (e.g. names of regions). You can click on the arrow next to a data frame name to see its variables. + +In Stata, this is most similar to the Variables Manager window. + +This pane also contains *History* where you can see commands that you can previously. It also has a "Tutorial" tab where you can complete interactive R tutorials if you have the **learnr** package installed. It also has a "Connections" pane for external connections, and can have a "Git" pane if you choose to interface with Github. + +**Plots, Viewer, Packages, and Help Pane**\ +The lower-right pane includes several important tabs. Typical plot graphics including maps will display in the Plot pane. Interactive or HTML outputs will display in the Viewer pane. The Help pane can display documentation and help files. The Files pane is a browser which can be used to open or delete files. The Packages pane allows you to see, install, update, delete, load/unload R packages, and see which version of the package you have. To learn more about packages see the [packages section](#packages) below. + +This pane contains the Stata equivalents of the Plots Manager and Project Manager windows. + +### RStudio settings {.unnumbered} + +Change RStudio settings and appearance in the *Tools* drop-down menu, by selecting *Global Options*. There you can change the default settings, including appearance/background color. + + +```{r out.width = c('50%'), fig.show='hold', echo=F} +knitr::include_graphics(here::here("images", "RStudio_tools_options_1.png")) + +knitr::include_graphics(here::here("images", "RStudio_tools_options.png")) +``` + + +**Restart** + +If your R freezes, you can re-start R by going to the Session menu and clicking "Restart R". This avoids the hassle of closing and opening RStudio. + + +**_CAUTION:_** Everything in your R environment will be removed when you do this. + +### Keyboard shortcuts {.unnumbered} + +Some very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux [Rstudio user interface cheatsheet](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html#keyboard-shortcuts). + ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Windows/Linux | Mac | Action | ++==================================+========================+================================================================================================================================+ +| Esc | Esc | Interrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing "+" in the R console). | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl+s | Cmd+s | Save (script). | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Tab | Tab | Auto-complete. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Enter | Cmd + Enter | Run current line(s)/selection of code. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Shift + C | Cmd + Shift + c | Comment/uncomment the highlighted lines. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Alt + - | Option + - | Insert `<-`. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Shift + m | Cmd + Shift + m | Insert `%>%`. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + l | Cmd + l | Clear the R console. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Alt + b | Cmd + Option + b | Run from start to current. line | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Alt + t | Cmd + Option + t | Run the current code section (R Markdown). | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Alt + i | Cmd + Shift + r | Insert code chunk (into R Markdown). | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Alt + c | Cmd + Option + c | Run current code chunk (R Markdown). | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| up/down arrows in R console | Same | Toggle through recently run commands. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Shift + up/down arrows in script | Same | Select multiple code lines. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + f | Cmd + f | Find and replace in current script. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Ctrl + Shift + f | Cmd + Shift + f | Find in files (search/replace across many scripts). | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Alt + l | Cmd + Option + l | Fold selected code. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ +| Shift + Alt + l | Cmd + Shift + Option+l | Unfold selected code. | ++----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+ + +[***TIP:*** Use your Tab key when typing to engage RStudio's auto-complete functionality. This can prevent spelling errors. Press Tab while typing to produce a drop-down menu of likely functions and objects, based on what you have typed so far.]{style="color: darkgreen;"} + + + +## Functions {#functions} + +Functions are at the core of using R. Functions are how you perform tasks and operations. Many functions come installed with R, many more are available for download in *packages* (explained in the [packages](#packages) section), and you can even write your own custom functions! + +This basics section on functions explains: + +- What a function is and how they work.\ +- What function *arguments* are.\ +- How to get help understanding a function. + +*A quick note on syntax:* In this handbook, functions are written in code-text with open parentheses, like this: `filter()`. As explained in the [packages](#packages) section, functions are downloaded within *packages*. In this handbook, package names are written in **bold**, like **dplyr**. Sometimes in example code you may see the function name linked explicitly to the name of its package with two colons (`::`) like this: `dplyr::filter()`. The purpose of this linkage is explained in the packages section. + + + +### Simple functions {.unnumbered} + +**A function is like a machine that receives inputs, carries out an action with those inputs, and produces an output.** What the output is depends on the function. + +**Functions typically operate upon some object placed within the function's parentheses**. For example, the function `sqrt()` calculates the square root of a number: + + +```{r basics_function_sqrt} +sqrt(49) +``` + + +The object provided to a function also can be a column in a dataset (see the [Objects](#objects) section for detail on all the kinds of objects). Because R can store multiple datasets, you will need to specify both the dataset and the column. One way to do this is using the `$` notation to link the name of the dataset and the name of the column (`dataset$column`). In the example below, the function `summary()` is applied to the numeric column `age` in the dataset `linelist`, and the output is a summary of the column's numeric and missing values. + + +```{r basics_functions_summary} +# Print summary statistics of column 'age' in the dataset 'linelist' +summary(linelist$age) +``` + + +[***NOTE:*** Behind the scenes, a function represents complex additional code that has been wrapped up for the user into one easy command.]{style="color: black;"} + + + +### Functions with multiple arguments {.unnumbered} + +Functions often ask for several inputs, called ***arguments***, located within the parentheses of the function, usually separated by commas. + +- Some arguments are required for the function to work correctly, others are optional.\ +- Optional arguments have default settings.\ +- Arguments can take character, numeric, logical (TRUE/FALSE), and other inputs. + +Here is a fun fictional function, called `oven_bake()`, as an example of a typical function. It takes an input object (e.g. a dataset, or in this example "dough") and performs operations on it as specified by additional arguments (`minutes =` and `temperature =`). The output can be printed to the console, or saved as an object using the assignment operator `<-`. + + +```{r basics_functions_image, echo=F, out.width = "75%", fig.align = "center"} +knitr::include_graphics(here::here("images", "Function_Bread_Example.png")) +``` + + +**In a more realistic example**, the `age_pyramid()` command below produces an age pyramid plot based on defined age groups and a binary split column, such as `gender`. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish `linelist` as the data frame to use, `age_cat5` as the column to count, and `gender` as the binary column to use for splitting the pyramid by color. + + +```{r basics_functions_arguments, include=FALSE, results='hide', message=FALSE, warning=FALSE,} +## create an age group variable by specifying categorical breaks +linelist$age_group <- cut(linelist$age, breaks = c(0, 5, 10, 15, 20, 30, 45, 60)) +``` + +```{r message=FALSE, warning=FALSE, out.width = "75%", out.height="75%"} +# Create an age pyramid +age_pyramid(data = linelist, age_group = "age_cat5", split_by = "gender") +``` + + +The above command can be equivalently written as below, in a longer style with a new line for each argument. This style can be easier to read, and easier to write "comments" with `#` to explain each part (commenting extensively is good practice!). To run this longer command you can highlight the entire command and click "Run", or just place your cursor in the first line and then press the Ctrl and Enter keys simultaneously. + + +```{r message=FALSE, warning=FALSE, out.width = "75%", out.height="75%"} +# Create an age pyramid +age_pyramid( + data = linelist, # use case linelist + age_group = "age_cat5", # provide age group column + split_by = "gender" # use gender column for two sides of pyramid + ) +``` + + +The first half of an argument assignment (e.g. `data =`) does not need to be specified if the arguments are written in a specific order (specified in the function's documentation). The below code produces the exact same pyramid as above, because the function expects the argument order: data frame, `age_group` variable, `split_by` variable. + + +```{r, basics_functions_pyramid2, eval = FALSE, warning=FALSE, message=FALSE, , out.width = "75%", out.height="75%", eval=F} +# This command will produce the exact same graphic as above +age_pyramid(linelist, "age_cat5", "gender") +``` + + +**A more complex `age_pyramid()` command might include the *optional* arguments to:** + +- Show proportions instead of counts (set `proportional = TRUE` when the default is `FALSE`)\ +- Specify the two colors to use (`pal =` is short for "palette" and is supplied with a vector of two color names. See the [objects](https://epirhandbook.com/new_pages/basics.html#objects) page for how the function `c()` makes a vector) + +[***NOTE:*** For arguments that you specify with both parts of the argument (e.g. `proportional = TRUE`), their order among all the arguments does not matter.]{style="color: black;"} + + +```{r message=FALSE, warning=FALSE, out.width = "75%", out.height="75%"} +age_pyramid( + linelist, # use case linelist + "age_cat5", # age group column + "gender", # split by gender + proportional = TRUE, # percents instead of counts + pal = c("orange", "purple") # colors + ) +``` + +***TIP:*** Remember that you can put `?` before a function to see what arguments the function can take, and which arguments are needed and which arguments have default values. For example `?age_pyramid'. + + + +### Writing Functions {.unnumbered} + +R is a language that is oriented around functions, so you should feel empowered to write your own functions. Creating functions brings several advantages: + +- To facilitate modular programming - the separation of code in to independent and manageable pieces.\ +- Replace repetitive copy-and-paste, which can be error prone.\ +- Give pieces of code memorable names. + +How to write a function is covered in-depth in the [Writing functions](writing_functions.qmd) page. + + + + +```{r, eval=F, echo=F} +my_function <- function( ARGUMENTS HERE ){ CODE HERE } +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +```{r eval=F, echo=F, message=FALSE, warning=FALSE, out.width = "75%", out.height="75%"} +staff_calc(daily_cases = 150) +``` + +```{r, eval=F, echo=F} +case_incidence <- tibble( + dates = seq.Date(from = as.Date("2020-05-01"), to = as.Date("2020-05-21"), by = 1), + projected_incidence = c(102,110,50,37,106,190,146,138,135,111,60,43,189,184,185,80,44,97,254,291,288), + staff_needed = staff_calc(projected_incidence) +) + +ggplot(case_incidence, aes(x = dates))+ + geom_line(aes(y = projected_incidence))+ + geom_line(aes(y = staff_needed)) +``` + + + + + + + + +## Packages {#packages} + +**Packages contain functions.** + +An R package is a shareable bundle of code and documentation that contains pre-defined functions. Users in the R community develop packages all the time catered to specific problems, it is likely that one can help with your work! You will install and use hundreds of packages in your use of R. + +On installation, R contains **"base"** packages and functions that perform common elementary tasks. But many R users create specialized functions, which are verified by the R community and which you can download as a **package** for your own use. In this handbook, package names are written in **bold**. One of the more challenging aspects of R is that there are often many functions or packages to choose from to complete a given task. + +### Install and load {.unnumbered} + +*Functions* are contained within **packages** which can be downloaded ("installed") to your computer from the internet. Once a package is downloaded, it is stored in your "library". You can then access the functions it contains during your current R session by "loading" the package. + +*Think of R as your personal library*: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow,"load", that book from your library. + +In summary: to use the functions available in an R package, 2 steps must be implemented: + +1) The package must be **installed** (once), *and*\ +2) The package must be **loaded** (each R session) + +#### Your library {.unnumbered} + +Your "library" is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called "win-library". For example: `R\win-library\4.4.1` (the 4.4.1 is the R version - you'll have a different library for each R version you've downloaded). + +You can print the file path to your library by entering `.libPaths()` (empty parentheses). This becomes especially important if working with [R on network drives](network_drives.qmd). + +#### Install from CRAN {.unnumbered} + +Most often, R users download packages from CRAN. CRAN (Comprehensive R Archive Network) is an online public warehouse of R packages that have been published by R community members. + +Are you worried about viruses and security when downloading a package from CRAN? Read [this article](https://support.rstudio.com/hc/en-us/articles/360042593974-R-and-R-Package-Security) on the topic. + +#### How to install and load {.unnumbered} + +In this handbook, we suggest using the **pacman** package (short for "package manager"). It offers a convenient function `p_load()` which will install a package if necessary *and* load it for use in the current R session. + +The syntax quite simple. Just list the names of the packages within the `p_load()` parentheses, separated by commas. This command will install the **rio**, **tidyverse**, and **here** packages if they are not yet installed, and will load them for use. This makes the `p_load()` approach convenient and concise if sharing scripts with others. + +Note that package names are case-sensitive. + + +```{r} +# Install (if necessary) and load packages for use +pacman::p_load(rio, tidyverse, here) +``` + + +Here we have used the syntax `pacman::p_load()` which explicitly writes the package name (**pacman**) prior to the function name (`p_load()`), connected by two colons `::`. This syntax is useful because it also loads the **pacman** package (assuming it is already installed). + +There are alternative **base** R functions that you will see often. The **base** R function for installing a package is `install.packages()`. The name of the package to install must be provided in the parentheses *in quotes*. If you want to install multiple packages in one command, they must be listed within a character vector `c()`. + +Note: this command *installs* a package, but does *not* load it for use in the current session. + + +```{r, eval=F} +# install a single package with base R +install.packages("tidyverse") + +# install multiple packages with base R +install.packages(c("tidyverse", "rio", "here")) +``` + + +Installation can also be accomplished point-and-click by going to the RStudio "Packages" pane and clicking "Install" and searching for the desired package name. + +The **base** R function to **load** a package for use (after it has been installed) is `library()`. It can load only one package at a time (another reason to use `p_load()`). You can provide the package name with or without quotes. + + +```{r, eval=F} +# load packages for use, with base R +library(tidyverse) +library(rio) +library(here) +``` + + +To check whether a package in installed or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session. + +**Install from Github** + +Sometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the *development version* with new features not yet offered in the more stable published CRAN version. These are often hosted on the website [github.com](https://github.com/) in a free, public-facing code "repository". Read more about Github in the handbook page on [Version control and collaboration with Git and Github](https://epirhandbook.com/new_pages/collaboration.html). + +To download R packages from Github, you can use the function `p_load_gh()` from **pacman**, which will install the package if necessary, and load it for use in your current R session. Alternatives to install include using the **remotes** or **devtools** packages. Read more about all the **pacman** functions in the [package documentation](https://cran.r-project.org/web/packages/pacman/pacman.pdf). + +To install from Github, you have to provide more information. You must provide: + +1) The Github ID of the repository owner +2) The name of the repository that contains the package\ +3) Optional: *The name of the "branch" (specific development version) you want to download* + +In the examples below, the first word in the quotation marks is the Github ID of the repository owner, after the slash is the name of the repository (the name of the package). + + +```{r, eval=F} +# install/load the epicontacts package from its Github repository +p_load_gh("reconhub/epicontacts") +``` + + +If you want to install from a "branch" (version) other than the main branch, add the branch name after an "\@", after the repository name. + + +```{r, eval=F} +# install the "timeline" branch of the epicontacts package from Github +p_load_gh("reconhub/epicontacts@timeline") +``` + + +If there is no difference between the Github version and the version on your computer, no action will be taken. You can "force" a re-install by instead using `p_load_current_gh()` with the argument `update = TRUE`. Read more about **pacman** in this [online vignette](http://trinker.github.io/pacman/vignettes/Introduction_to_pacman.html) + +**Install from ZIP or TAR** + +You could install the package from a URL: + + +```{r, eval=F} +packageurl <- "https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz" +install.packages(packageurl, repos=NULL, type="source") +``` + + +Or, download it to your computer in a zipped file: + +Option 1: using `install_local()` from the **remotes** package + + +```{r, eval=F} +remotes::install_local("~/Downloads/dplyr-master.zip") +``` + + +Option 2: using `install.packages()` from **base** R, providing the file path to the ZIP file and setting `type = "source` and `repos = NULL`. + + +```{r, eval=F} +install.packages("~/Downloads/dplyr-master.zip", repos=NULL, type="source") +``` + + +### Code syntax {.unnumbered} + +For clarity in this handbook, functions are sometimes preceded by the name of their package using the `::` symbol in the following way: `package_name::function_name()` + +Once a package is loaded for a session, this explicit style is not necessary. One can just use `function_name()`. However writing the package name is useful when a function name is common and may exist in multiple packages (e.g. `plot()`). Writing the package name will also load the package if it is not already loaded. + + +```{r eval=FALSE} +# This command uses the package "rio" and its function "import()" to import a dataset +linelist <- rio::import("linelist.xlsx", which = "Sheet1") +``` + + +### Function help {.unnumbered} + +To read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like `?thefunctionname` (for example, to get help for the function `p_load` you would write `?p_load`) and the Help page will appear in the Help pane. Finally, try searching online for resources. + +### Update packages {.unnumbered} + +You can update packages by re-installing them. You can also click the green "Update" button in your RStudio Packages pane to see which packages have new versions to install. Be aware that your old code may need to be updated if there is a major revision to how a function works! + +### Delete packages {.unnumbered} + +Use `p_delete()` from **pacman**, or `remove.packages()` from **base** R. + +### Dependencies {.unnumbered} + +Packages often depend on other packages to work. These are called dependencies. If a dependency fails to install, then the package depending on it may also fail to install. + +See the dependencies of a package with `p_depends()`, and see which packages depend on it with `p_depends_reverse()` + +### Masked functions {.unnumbered} + +It is not uncommon that two or more packages contain the same function name. For example, the package **dplyr** has a `filter()` function, but so does the package **stats**. The default `filter()` function depends on the order these packages are first loaded in the R session - the later one will be the default for the command `filter()`. + +You can check the order in your Environment pane of R Studio - click the drop-down for "Global Environment" and see the order of the packages. Functions from packages *lower* on that drop-down list will mask functions of the same name in packages that appear higher in the drop-down list. When first loading a package, R will warn you in the console if masking is occurring, but this can be easy to miss. + + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "masking_functions.png")) +``` + + +Here are ways you can fix masking: + +1) Specify the package name in the command. For example, use `dplyr::filter()`\ +2) Re-arrange the order in which the packages are loaded (e.g. within `p_load()`), and **start a new R session** + +### Detach / unload {.unnumbered} + +To detach (unload) a package, use this command, with the correct package name and only one colon. Note that this may not resolve masking. + + +```{r, eval=F} +detach(package:PACKAGE_NAME_HERE, unload=TRUE) +``` + + +### Install older version {.unnumbered} + +See this [guide](https://support.rstudio.com/hc/en-us/articles/219949047-Installing-older-versions-of-packages) to install an older version of a particular package. + +### Suggested packages {.unnumbered} + +See the page on [Suggested packages](packages_suggested.qmd) for a listing of packages we recommend for everyday epidemiology. + + + +## Scripts {#scripts} + +Scripts are a fundamental part of programming. They are documents that hold your commands (e.g. functions to create and modify datasets, print visualizations, etc). You can save a script and run it again later. There are many advantages to storing and running your commands from a script (vs. typing commands one-by-one into the R console "command line"): + +- Portability - you can share your work with others by sending them your scripts.\ +- Reproducibility - so that you and others know exactly what you did.\ +- Version control - so you can track changes made by yourself or colleagues.\ +- Commenting/annotation - to explain to your colleagues what you have done. + +### Commenting {.unnumbered} + +In a script you can also annotate ("comment") around your R code. Commenting is helpful to explain to yourself and other readers what you are doing. You can add a comment by typing the hash symbol (\#) and writing your comment after it. The commented text will appear in a different color than the R code. + +Any code written after the \# will not be run. Therefore, placing a \# before code is also a useful way to temporarily block a line of code ("comment out") if you do not want to delete it. You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac). + + +```{r, eval = F} +# A comment can be on a line by itself +# import data +linelist <- import("linelist_raw.xlsx") %>% # a comment can also come after code +# filter(age > 50) # It can also be used to deactivate / remove a line of code + count() + +``` + + +There are a few general ideas to follow when writing your scripts in order to make them accessible. +- Add comments on *what* you are doing *and on **why** you are doing it*.\ +- Break your code into logical sections.\ +- Accompany your code with a text step-by-step description of what you are doing (e.g. numbered steps). + +### Style {.unnumbered} + +It is important to be conscious of your coding style - especially if working on a team. We advocate for the **tidyverse** [style guide](https://style.tidyverse.org/). There are also packages such as **styler** and **lintr** which help you conform to this style. + +A few very basic points to make your code readable to others:\ +\* When naming objects, use only lowercase letters, numbers, and underscores `_`, e.g. `my_data`\ +\* Use frequent spaces, including around operators, e.g. `n = 1` and `age_new <- age_old + 3` + +### Example Script {.unnumbered} + +Below is an example of a short R script. Remember, the better you succinctly explain your code in comments, the more your colleagues will like you! + + +```{r out.width = "100%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "example_script.png")) +``` + + + + +### R markdown and Quarto {.unnumbered} + +An [R Markdown](https://rmarkdown.rstudio.com/) or [Quarto](https://quarto.org/docs/computations/r.html) script are types of R script in which the script itself *becomes* an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports. + +Even this website and handbook is produced with Quarto scripts! + +It is worth noting that beginner R users can also use R Markdown - do not be intimidated! To learn more, see the handbook page on [Reports with R Markdown](rmarkdown.qmd) documents. + + + +### R notebooks {.unnumbered} + +There is no difference between writing in a Rmarkdown vs an R notebook. However the execution of the document differs slightly. See this [site](http://uc-r.github.io/r_notebook) for more details. + + + +### Shiny {.unnumbered} + +Shiny apps/websites are contained within one script, which must be named `app.R`. This file has three components: + +1) A user interface (ui).\ +2) A server function.\ +3) A call to the `shinyApp` function. + +See the handbook page on [Dashboards with Shiny](shiny_basics.qmd), or this online tutorial: [Shiny tutorial](https://shiny.rstudio.com/tutorial/written-tutorial/lesson1/) + +*In previous versions, the above file was split into two files (`ui.R` and `server.R`)* + +### Code folding {.unnumbered} + +You can collapse portions of code to make your script easier to read. + +To do this, create a text header with \#, write your header, and follow it with at least 4 of either dashes (-), hashes (\#) or equals (=). When you have done this, a small arrow will appear in the "gutter" to the left (by the row number). You can click this arrow and the code below until the next header will collapse and a dual-arrow icon will appear in its place. + +To expand the code, either click the arrow in the gutter again, or the dual-arrow icon. There are also keyboard shortcuts as explained in the [RStudio section](#rstudio) of this page. + +By creating headers with \#, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more \# symbols, for example \# for primary, \#\# for secondary, and \#\#\# for tertiary headers. + +Below are two versions of an example script. On the left is the original with commented headers. On the right, four dashes have been written after each header, making them collapsible. Two of them have been collapsed, and you can see that the Table of Contents at the bottom now shows each section. + + +```{r, out.width = c('50%'), fig.show='hold', echo=F} +knitr::include_graphics(here::here("images", "code_folding1.png")) +knitr::include_graphics(here::here("images", "code_folding2.png")) +``` + + +Other areas of code that are automatically eligible for folding include "braced" regions with brackets `{ }` such as function definitions or conditional blocks (if else statements). You can read more about code folding at the RStudio [site](https://support.rstudio.com/hc/en-us/articles/200484568-Code-Folding-and-Sections). + + + + + + + +## Working directory + +The working directory is the root folder location used by R for your work - where R looks for and saves files by default. By default, it will save new files and outputs to this location, and will look for files to import (e.g. datasets) here as well. + +The working directory appears in grey text at the top of the RStudio Console pane. You can also print the current working directory by running `getwd()` (leave the parentheses empty). + + +```{r out.width = "100%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "working_directory_1.png")) +``` + + +### Recommended approach {.unnumbered} + +**See the page on [R projects](r_projects.qmd) for details on our recommended approach to managing your working directory.**\ + +A common, efficient, and trouble-free way to manage your working directory and file paths is to combine these 3 elements in an [R project](r_projects.qmd)-oriented workflow: + +1) An R Project to store all your files (see page on [R projects](r_projects.qmd))\ +2) The **here** package to locate files (see page on [Import and export](importing.qmd))\ +3) The **rio** package to import/export files (see page on [Import and export](importing.qmd)) + + + +### Set by command {.unnumbered} + +Until recently, many people learning R were taught to begin their scripts with a `setwd()` command. + +Please instead consider using an [R project](r_projects.qmd)-oriented workflow and read the [reasons for not using `setwd()`](https://www.tidyverse.org/blog/2017/12/workflow-vs-script/). + +In brief, your work becomes specific to your computer. This means that file paths used to import and export files need to be changed if used on a different computer, or by different collaborators. + +As noted above, although we do not recommend this approach in most circumstances, you can use the command `setwd()` with the desired folder file path in quotations, for example: + + +```{r, eval=F} +setwd("C:/Documents/R Files/My analysis") +``` + + +[***DANGER:*** Setting a working directory with `setwd()` *can* be "brittle" if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory, such as with the [**here** package]. ]{style="color: red;"} + + + +### Set manually {.unnumbered} + +To set the working directory manually (the point-and-click equivalent of `setwd()`), click the Session drop-down menu and go to "Set Working Directory" and then "Choose Directory". This will set the working directory for that specific R session. Note: if using this approach, you will have to do this manually each time you open RStudio. + + + +### Within an R project {.unnumbered} + +If using an R project, the working directory will default to the R project root folder that contains the ".rproj" file. This will apply if you open RStudio by clicking open the R Project (the file with ".rproj" extension). + + + +### Working directory in an R markdown {.unnumbered} + +In an R markdown script, the default working directory is the folder the Rmarkdown file (`.Rmd`) is saved within. If using an R project and **here** package, this does not apply and the working directory will be `here()` as explained in the [R projects](r_projects.qmd) page. + +If you want to change the working directory of a stand-alone R markdown (not in an R project), if you use `setwd()` this will only apply to that specific code chunk. To make the change for all code chunks in an R markdown, edit the setup chunk to add the `root.dir =` parameter, such as below: + + +```{r, eval=F} +knitr::opts_knit$set(root.dir = 'desired/directorypath') +``` + + +It is much easier to just use the R markdown within an R project and use the **here** package. + + + +### Providing file paths {.unnumbered} + +Perhaps the most common source of frustration for an R beginner (at least on a Windows machine) is typing in a file path to import or export data. There is a thorough explanation of how to best input file paths in the [Import and export](importing.qmd) page, but here are a few key points: + +**Broken paths** + +Below is an example of an "absolute" or "full address" file path. These will likely break if used by another computer. One exception is if you are using a shared/network drive. + + C:/Users/Name/Document/Analytic Software/R/Projects/Analysis2019/data/March2019.csv + +**Slash direction** + +*If typing in a file path, be aware the direction of the slashes.* + +Use *forward slashes* (`/`) to separate the components ("data/provincial.csv"). For Windows users, the default way that file paths are displayed is with *back slashes* (\\) - so you will need to change the direction of each slash. If you use the **here** package as described in the [R projects](r_projects.qmd) page the slash direction is not an issue. + +**Relative file paths** + +We generally recommend providing "relative" filepaths instead - that is, the path *relative to* the root of your R Project. You can do this using the **here** package as explained in the [R projects](r_projects.qmd) page. A relativel filepath might look like this: + + +```{r, eval=F} +# Import csv linelist from the data/linelist/clean/ sub-folders of an R project +linelist <- import(here("data", "clean", "linelists", "marin_country.csv")) +``` + + +Even if using relative file paths within an R project, you can still use absolute paths to import and export data outside your R project. + + + +## Objects {#objects} + +Everything in R is an object, and R is an "object-oriented" language. These sections will explain: + +- How to create objects (`<-`). +- Types of objects (e.g. data frames, vectors..).\ +- How to access subparts of objects (e.g. variables in a dataset).\ +- Classes of objects (e.g. numeric, logical, integer, double, character, factor). + + + +### Everything is an object {.unnumbered} + +*This section is adapted from the [R4Epis project](https://r4epis.netlify.app/training/r_basics/objects/).*\ + +Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are **objects** which are **assigned a name** and **can be referenced** in later commands. + +An object exists when you have assigned it a value (see the assignment section below). When it is assigned a value, the object appears in the Environment (see the upper right pane of RStudio). It can then be operated upon, manipulated, changed, and re-defined. + + + +### Defining objects (`<-`) {.unnumbered} + +**Create objects *by assigning them a value* with the \<- operator.**\ +You can think of the assignment operator `<-` as the words "is defined as". Assignment commands generally follow a standard order: + +**object_name** \<- **value** (or process/calculation that produce a value) + +For example, you may want to record the current epidemiological reporting week as an object for reference in later code. In this example, the object `current_week` is created when it is assigned the value `"2018-W10"` (the quote marks make this a character value). The object `current_week` will then appear in the RStudio Environment pane (upper-right) and can be referenced in later commands. + +See the R commands and their output in the boxes below. + + +```{r basics_objects_assignment} +current_week <- "2018-W10" # this command creates the object current_week by assigning it a value +current_week # this command prints the current value of current_week object in the console +``` + + +[***NOTE:*** Note the `[1]` in the R console output is simply indicating that you are viewing the first item of the output]{style="color: black;"} + +[***CAUTION:*** **An object's value can be over-written** at any time by running an assignment command to re-define its value. Thus, the **order of the commands run is very important**.]{style="color: orange;"} + +The following command will re-define the value of `current_week`: + + +```{r basics_objects_reassignment} +current_week <- "2018-W51" # assigns a NEW value to the object current_week +current_week # prints the current value of current_week in the console +``` + + +**Equals signs `=`** + +You will also see equals signs in R code: + +- A double equals sign `==` between two objects or values asks a logical *question*: "is this equal to that?".\ +- You will also see equals signs within functions used to specify values of function arguments (read about these in sections below), for example `max(age, na.rm = TRUE)`.\ +- You *can* use a single equals sign `=` in place of `<-` to create and define objects, but this is discouraged. You can read about why this is discouraged [here](https://renkun.me/2014/01/28/difference-between-assignment-operators-in-r/). + +**Datasets** + +Datasets are also objects (typically "data frames") and must be assigned names when they are imported. In the code below, the object `linelist` is created and assigned the value of a CSV file imported with the **rio** package and its `import()` function. + + +```{r basics_objects_data frames, eval=FALSE} +# linelist is created and assigned the value of the imported CSV file +linelist <- import("my_linelist.csv") +``` + + +You can read more about importing and exporting datasets with the section on [Import and export](importing.qmd). + +[***CAUTION:*** A quick note on naming of objects:]{style="color: orange;"} + +- Object names must not contain spaces, but you should use underscore (\_) or a period (.) instead of a space.\ +- Object names are case-sensitive (meaning that Dataset_A is different from dataset_A). +- Object names must begin with a letter (they cannot begin with a number like 1, 2 or 3). + +**Outputs** + +Outputs like tables and plots provide an example of how outputs can be saved as objects, or just be printed without being saved. A cross-tabulation of gender and outcome using the **base** R function `table()` can be printed directly to the R console (*without* being saved). + + +```{r} +# printed to R console only +table(linelist$gender, linelist$outcome) +``` + + +But the same table can be saved as a named object. Then, optionally, it can be printed. + + +```{r} +# save +gen_out_table <- table(linelist$gender, linelist$outcome) + +# print +gen_out_table +``` + + +**Columns** + +Columns in a dataset are also objects and can be defined, over-written, and created as described below in the section on [Columns](https://epirhandbook.com/new_pages/basics.html#columnsvariables). + +You can use the assignment operator from **base** R to create a new column. Below, the new column `bmi` (Body Mass Index) is created, and for each row the new value is result of a mathematical operation on the row's value in the `wt_kg` and `ht_cm` columns. + + +```{r, eval=F} +# create new "bmi" column using base R syntax +linelist$bmi <- linelist$wt_kg / (linelist$ht_cm/100)^2 +``` + + +However, in this handbook, we emphasize a different approach to defining columns, which uses the function `mutate()` from the **dplyr** package and *piping* with the pipe operator (`%>%`). The syntax is easier to read and there are other advantages explained in the page on [Cleaning data and core functions](cleaning.qmd). You can read more about *piping* in the Piping section below. + + +```{r, eval=F} +# create new "bmi" column using dplyr syntax +linelist <- linelist %>% + mutate(bmi = wt_kg / (ht_cm/100)^2) +``` + + + + +### Object structure {.unnumbered} + +**Objects can be a single piece of data (e.g. `my_number <- 24`), or they can consist of structured data.** + +The graphic below is borrowed from [this online R tutorial](http://venus.ifca.unican.es/Rintro/dataStruct.html). It shows some common data structures and their names. Not included in this image is spatial data, which is discussed in the [GIS basics](gis.qmd) page. + + +```{r basics_objects_structures, echo=F, out.width = "75%", out.height="50%", fig.align = "center"} +knitr::include_graphics(here::here("images", "R_data_structures.png")) +``` + + +In epidemiology (and particularly field epidemiology), you will *most commonly* encounter data frames and vectors: + ++------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ +| Common structure | Explanation | Example | ++------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ + +| Vectors | A container for a sequence of singular objects, all of the same class (e.g. numeric, character). | **"Variables" (columns) in data frames are vectors** (e.g. the column `age_years`). | ++------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ +| Data Frames | Vectors (e.g. columns) that are bound together that all have the same number of rows. | `linelist` is a data frame. | ++------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+ + +Note that to create a vector that "stands alone" (is not part of a data frame) the function `c()` is used to combine the different elements. For example, if creating a vector of colors plot's color scale: `vector_of_colors <- c("blue", "red2", "orange", "grey")` + + + +### Object classes {.unnumbered} + +All the objects stored in R have a *class* which tells R how to handle the object. There are many possible classes, but common ones include: + ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| Class | Explanation | Examples | ++============+=========================================================================================================================================================================================+=======================================================================================================+ +| Character | These are text/words/sentences **"within quotation marks"**. Math cannot be done on these objects. | "Character objects are in quotation marks" | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| Integer | Numbers that are **whole only** (no decimals) | -5, 14, or 2000 | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| Numeric | These are numbers and **can include decimals**. If within quotation marks they will be considered character class. | 23.1 or 14 | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| Factor | These are vectors that have a **specified order** or hierarchy of values | An variable of economic status with ordered values | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| Date | **Once R is told that certain data are Dates**, these data can be manipulated and displayed in special ways. See the page on [Working with dates](dates.qmd) for more information. | 2018-04-12 or 15/3/1954 or Wed 4 Jan 1980 ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| Logical | Values must be one of the two special values TRUE or FALSE (note these are **not** "TRUE" and "FALSE" in quotation marks) | TRUE or FALSE | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| data.frame | A data frame is how R stores a **typical dataset**. It consists of vectors (columns) of data bound together, that all have the same number of observations (rows). | The example AJS dataset named `linelist_raw` contains 68 variables with 300 observations (rows) each. | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| tibble | tibbles are a variation on data frame, the main operational difference being that they print more nicely to the console (display first 10 rows and only columns that fit on the screen) | Any data frame, list, or matrix can be converted to a tibble with `as_tibble()` | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ +| list | A list is like vector, but holds other objects that can be other different classes | A list could hold a single number, and a data frame, and a vector, and even another list within it! | ++------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+ + +**You can test the class of an object by providing its name to the function `class()`**. Note: you can reference a specific column within a dataset using the `$` notation to separate the name of the dataset and the name of the column. + + +```{r, echo=TRUE,} +class(linelist) # class should be a data frame or tibble + +class(linelist$age) # class should be numeric + +class(linelist$gender) # class should be character +``` + + +Sometimes, a column will be converted to a different class automatically by R. Watch out for this! For example, if you have a vector or column of numbers, but a character value is inserted... the entire column will change to class character. + + +```{r} +num_vector <- c(1, 2 , 3, 4, 5) # define vector as all numbers +class(num_vector) # vector is numeric class +num_vector[3] <- "three" # convert the third element to a character +class(num_vector) # vector is now character class +``` + + +One common example of this is when manipulating a data frame in order to print a table - if you make a total row and try to paste/glue together percents in the same cell as numbers (e.g. `23 (40%)`), the entire numeric column above will convert to character and can no longer be used for mathematical calculations.**Sometimes, you will need to convert objects or columns to another class.** + ++------------------+---------------------------------------------------------------------------------------+ +| Function | Action | ++==================+=======================================================================================+ +| `as.character()` | Converts to character class | ++------------------+---------------------------------------------------------------------------------------+ +| `as.numeric()` | Converts to numeric class | ++------------------+---------------------------------------------------------------------------------------+ +| `as.integer()` | Converts to integer class | ++------------------+---------------------------------------------------------------------------------------+ +| `as.Date()` | Converts to Date class - Note: see section on [dates](dates.qmd) for details | ++------------------+---------------------------------------------------------------------------------------+ +| `factor()` | Converts to factor - Note: re-defining order of value levels requires extra arguments | ++------------------+---------------------------------------------------------------------------------------+ + +Likewise, there are **base** R functions to check whether an object IS of a specific class, such as `is.numeric()`, `is.character()`, `is.double()`, `is.factor()`, `is.integer()` + +Here is [more online material on classes and data structures in R](https://swcarpentry.github.io/r-novice-inflammation/13-supp-data-structures.html). + + + +### Columns/Variables (`$`) {.unnumbered} + +**A column in a data frame is technically a "vector" (see table above)** - a series of values that must all be the same class (either character, numeric, logical, etc). + +A vector can exist independent of a data frame, for example a vector of column names that you want to include as explanatory variables in a model. To create a "stand alone" vector, use the `c()` function as below: + + +```{r, warning=F, message=F} +# define the stand-alone vector of character values +explanatory_vars <- c("gender", "fever", "chills", "cough", "aches", "vomit") + +# print the values in this named vector +explanatory_vars +``` + + +**Columns in a data frame are also vectors and can be called, referenced, extracted, or created using the `$` symbol.** The `$` symbol connects the name of the column to the name of its data frame. In this handbook, we try to use the word "column" instead of "variable". + + +```{r basics_objects_call, eval=F} +# Retrieve the length of the vector age_years +length(linelist$age) # (age is a column in the linelist data frame) + +``` + + +By typing the name of the data frame followed by `$` you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes! + + +```{r echo=F, out.width = "100%", fig.align = "center"} +knitr::include_graphics(here::here("images", "Calling_Names.gif")) +``` + + +[***ADVANCED TIP:*** Some more complex objects (e.g. a list, or an `epicontacts` object) may have multiple levels which can be accessed through multiple dollar signs. For example `epicontacts$linelist$date_onset`]{style="color: darkgreen;"} + + + +### Access/index with brackets (`[ ]`) {.unnumbered} + +You may need to view parts of objects, also called "indexing", which is often done using the square brackets `[ ]`. Using `$` on a data frame to access a column is also a type of indexing. + + +```{r} +my_vector <- c("a", "b", "c", "d", "e", "f") # define the vector +my_vector[5] # print the 5th element +``` + + +Square brackets also work to return specific parts of an returned output, such as the output of a `summary()` function: + + +```{r} +# All of the summary +summary(linelist$age) + +# Just the second element of the summary, with name (using only single brackets) +summary(linelist$age)[2] + +# Just the second element, without name (using double brackets) +summary(linelist$age)[[2]] + +# Extract an element by name, without showing the name +summary(linelist$age)[["Median"]] + +``` + + +Brackets also work on data frames to view specific rows and columns. You can do this using the syntax `data frame[rows, columns]`: + + +```{r basics_objects_access, eval=F} +# View a specific row (2) from dataset, with all columns (don't forget the comma!) +linelist[2,] + +# View all rows, but just one column +linelist[, "date_onset"] + +# View values from row 2 and columns 5 through 10 +linelist[2, 5:10] + +# View values from row 2 and columns 5 through 10 and 18 +linelist[2, c(5:10, 18)] + +# View rows 2 through 20, and specific columns +linelist[2:20, c("date_onset", "outcome", "age")] + +# View rows and columns based on criteria +# *** Note the data frame must still be named in the criteria! +linelist[linelist$age > 25 , c("date_onset", "outcome", "age")] + +# Use View() to see the outputs in the RStudio Viewer pane (easier to read) +# *** Note the capital "V" in View() function +View(linelist[2:20, "date_onset"]) + +# Save as a new object +new_table <- linelist[2:20, c("date_onset")] +``` + + +Note that you can also achieve the above row/column indexing on data frames and tibbles using **dplyr** syntax (functions `filter()` for rows, and `select()` for columns). Read more about these core functions in the [Cleaning data and core functions](cleaning.qmd) page. + +To filter based on "row number", you can use the **dplyr** function `row_number()` with open parentheses as part of a logical filtering statement. Often you will use the `%in%` operator and a range of numbers as part of that logical statement, as shown below. To see the *first* N rows, you can also use the special **dplyr** function `head()`. Note, there is a function `head()` from **base** R, but this is overwritten by the **dplyr** function when you load **tidyverse**. + + +```{r, eval=F} +# View first 100 rows +linelist %>% head(100) + +# Show row 5 only +linelist %>% filter(row_number() == 5) + +# View rows 2 through 20, and three specific columns (note no quotes necessary on column names) +linelist %>% + filter(row_number() %in% 2:20) %>% + select(date_onset, outcome, age) +``` + + +When indexing an object of class **list**, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list. Brackets can also be written after one another, as demonstrated below. + +This [visual explanation of lists indexing, with pepper shakers](https://r4ds.had.co.nz/vectors.html#lists-of-condiments) is humorous and helpful. + + +```{r} +# define demo list +my_list <- list( + # First element in the list is a character vector + hospitals = c("Central", "Empire", "Santa Anna"), + + # second element in the list is a data frame of addresses + addresses = data.frame( + street = c("145 Medical Way", "1048 Brown Ave", "999 El Camino"), + city = c("Andover", "Hamilton", "El Paso") + ) + ) +``` + + +Here is how the list looks when printed to the console. See how there are two named elements: + +- `hospitals`, a character vector\ +- `addresses`, a data frame of addresses + + +```{r} +my_list +``` + + +Now we extract, using various methods: + + +```{r} +my_list[1] # this returns the element in class "list" - the element name is still displayed + +my_list[[1]] # this returns only the (unnamed) character vector + +my_list[["hospitals"]] # you can also index by name of the list element + +my_list[[1]][3] # this returns the third element of the "hospitals" character vector + +my_list[[2]][1] # This returns the first column ("street") of the address data frame + +``` + + + + +### Remove objects {.unnumbered} + +You can remove individual objects from your R environment by putting the name in the `rm()` function (no quote marks): + + +```{r, eval=F} +rm(object_name) +``` + + +You can remove all objects (clear your workspace) by running: + + +```{r, eval=F} +rm(list = ls(all = TRUE)) +``` + + + + + + + + +## Piping (`%>%`) + +**Two general approaches to working with objects are:** + +1) **Pipes/tidyverse** - pipes send an object from function to function - emphasis is on the *action*, not the object.\ +2) **Define intermediate objects** - an object is re-defined again and again - emphasis is on the object. + + + +### **Pipes** {.unnumbered} + +**Simply explained, the pipe operator passes an intermediate output from one function to the next.**\ +You can think of it as saying "and then". Many functions can be linked together with `%>%`. + +- **Piping emphasizes a sequence of actions, not the object the actions are being performed on**.\ +- Pipes are best when a sequence of actions must be performed on one object.\ +- Pipes can make code more clean and easier to read, more intuitive. + +Pipe operators were first introduced through the [magrittr package](https://magrittr.tidyverse.org/), which is part of tidyverse, and were specified as `%>%`. In R 4.1.0, they introduced a **base** R pipe which is specified through `|>`. The behaviour of the two pipes is the same, and they can be used somewhat interchangeably. However, there are a few key differences. + +* The `%>%` pipe allows you to pass multiple arguments. +* The `%>%` pipe lets you drop parenthesis when calling a function with no other arguments (i.e. drop vs drop()). +* The `%>%` pipe allows you to start a pipe with `.` to create a function in your linking of code. + +For these reasons, we recommend the **magrittr** pipe, `%>%`, over the **base** R pipe, `|>`. + +To read more about the differences between base R and tidyverse (magrittr) pipes, see this [blog post](https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/). For more information on the tidyverse approach, please see this [style guide](https://style.tidyverse.org/pipes.html). + +Here is a fake example for comparison, using fictional functions to "bake a cake". First, the pipe method: + + +```{r piping_example_pipe, eval=F} +# A fake example of how to bake a cake using piping syntax + +cake <- flour %>% # to define cake, start with flour, and then... + add(eggs) %>% # add eggs + add(oil) %>% # add oil + add(water) %>% # add water + mix_together( # mix together + utensil = spoon, + minutes = 2) %>% + bake(degrees = 350, # bake + system = "fahrenheit", + minutes = 35) %>% + let_cool() # let it cool down +``` + + +Note that just like other R commands, pipes can be used to just display the result, or to save/re-save an object, depending on whether the assignment operator `<-` is involved. See both below: + + +```{r, eval=F} +# Create or overwrite object, defining as aggregate counts by age category (not printed) +linelist_summary <- linelist %>% + count(age_cat) +``` + +```{r} +# Print the table of counts in the console, but don't save it +linelist %>% + count(age_cat) +``` + + +**`%<>%`**\ +This is an "assignment pipe" from the **magrittr** package, which *pipes an object forward and also re-defines the object*. It must be the first pipe operator in the chain. It is shorthand. The below two commands are equivalent: + + +```{r, eval=F} +linelist <- linelist %>% + filter(age > 50) + +linelist %<>% filter(age > 50) +``` + + + + +### Define intermediate objects {.unnumbered} + +This approach to changing objects/data frames may be better if: + +- You need to manipulate multiple objects\ +- There are intermediate steps that are meaningful and deserve separate object names + +**Risks:** + +- Creating new objects for each step means creating lots of objects. If you use the wrong one you might not realize it!\ +- Naming all the objects can be confusing.\ +- Errors may not be easily detectable. + +Either name each intermediate object, or overwrite the original, or combine all the functions together. All come with their own risks. + +Below is the same fake "cake" example as above, but using this style: + + +```{r piping_example_redefine, eval=F} +# a fake example of how to bake a cake using this method (defining intermediate objects) +batter_1 <- left_join(flour, eggs) +batter_2 <- left_join(batter_1, oil) +batter_3 <- left_join(batter_2, water) + +batter_4 <- mix_together(object = batter_3, utensil = spoon, minutes = 2) + +cake <- bake(batter_4, degrees = 350, system = "fahrenheit", minutes = 35) + +cake <- let_cool(cake) +``` + + +Combine all functions together - this is difficult to read: + + +```{r eval=F} +# an example of combining/nesting mutliple functions together - difficult to read +cake <- let_cool(bake(mix_together(batter_3, utensil = spoon, minutes = 2), degrees = 350, system = "fahrenheit", minutes = 35)) +``` + + + + +## Key operators and functions {#operators} + +This section details operators in R, such as: + +- Definitional operators.\ +- Relational operators (less than, equal too..).\ +- Logical operators (and, or...).\ +- Handling missing values.\ +- Mathematical operators and functions (+/-, \>, sum(), median(), ...).\ +- The `%in%` operator. + + + +### Assignment operators {.unnumbered} + +**`<-`** + +The basic assignment operator in R is `<-`. Such that `object_name <- value`.\ +This assignment operator can also be written as `=`. We advise use of `<-` for general R use. We also advise surrounding such operators with spaces, for readability. + +**`<<-`** + +If [Writing functions](writing_functions.qmd), or using R in an interactive way with sourced scripts, then you may need to use this assignment operator `<<-` (from **base** R). This operator is used to define an object in a higher 'parent' R Environment. See this [online reference](https://stat.ethz.ch/R-manual/R-devel/library/base/html/assignOps.html). + +**`%<>%`** + +This is an "assignment pipe" from the **magrittr** package, which pipes an object forward and *also re-defines the object*. It must be the first pipe operator in the chain. It is shorthand, as shown below in two equivalent examples: + + +```{r, eval=F} +linelist <- linelist %>% + mutate(age_months = age_years * 12) +``` + + +The above is equivalent to the below: + + +```{r, eval=F} +linelist %<>% mutate(age_months = age_years * 12) +``` + + +**`%<+%`** + +This is used to add data to phylogenetic trees with the **ggtree** package. See the page on [Phylogenetic trees](phylogenetic_trees.qmd) or this online [resource book](https://yulab-smu.top/treedata-book/). + + + +### Relational and logical operators {.unnumbered} + +Relational operators compare values and are often used when defining new variables and subsets of datasets. Here are the common relational operators in R: + ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Meaning | Operator | Example | Example Result | ++==========================+============+==============+========================================================================================================================================================+ +| Equal to | `==` | `"A" == "a"` | `FALSE` (because R is case sensitive) *Note that == (double equals) is different from = (single equals), which acts like the assignment operator `<-`* | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Not equal to | `!=` | `2 != 0` | `TRUE` | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Greater than | `>` | `4 > 2` | `TRUE` | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Less than | `<` | `4 < 2` | `FALSE` | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Greater than or equal to | `>=` | `6 >= 4` | `TRUE` | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Less than or equal to | `<=` | `6 <= 4` | `FALSE` | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Value is missing | `is.na()` | `is.na(7)` | `FALSE` (see page on [Missing data](missing_data.qmd)) | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Value is not missing | `!is.na()` | `!is.na(7)` | `TRUE` | ++--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + +Logical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses `( )` for grouping and order of application. + ++---------------------+-----------------------------------------------------------------------+ +| Meaning | Operator | ++=====================+=======================================================================+ +| AND | `&` | ++---------------------+-----------------------------------------------------------------------+ +| OR | `|` (vertical bar) | ++---------------------+-----------------------------------------------------------------------+ +| Parentheses | `( )` Used to group criteria together and clarify order of operations | ++---------------------+-----------------------------------------------------------------------+ + +For example, below, we have a linelist with two variables we want to use to create our case definition, `hep_e_rdt`, a test result and `other_cases_in_hh`, which will tell us if there are other cases in the household. The command below uses the function `case_when()` to create the new variable `case_def` such that: + + +```{r eval=FALSE} +linelist_cleaned <- linelist %>% + mutate(case_def = case_when( + is.na(rdt_result) & is.na(other_case_in_home) ~ NA_character_, + rdt_result == "Positive" ~ "Confirmed", + rdt_result != "Positive" & other_cases_in_home == "Yes" ~ "Probable", + TRUE ~ "Suspected" + )) +``` + + ++------------------------------------------------------------------------------------------------+--------------------------------------------+ +| Criteria in example above | Resulting value in new variable "case_def" | ++================================================================================================+============================================+ +| If the value for variables `rdt_result` and `other_cases_in_home` are missing | `NA` (missing) | ++------------------------------------------------------------------------------------------------+--------------------------------------------+ +| If the value in `rdt_result` is "Positive" | "Confirmed" | ++------------------------------------------------------------------------------------------------+--------------------------------------------+ +| If the value in `rdt_result` is NOT "Positive" AND the value in `other_cases_in_home` is "Yes" | "Probable" | ++------------------------------------------------------------------------------------------------+--------------------------------------------+ +| If one of the above criteria are not met | "Suspected" | ++------------------------------------------------------------------------------------------------+--------------------------------------------+ + +*Note that R is case-sensitive, so "Positive" is different than "positive".* + + + +### Missing values {.unnumbered} + +In R, missing values are represented by the special value `NA` (a "reserved" value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, "Missing"), you may want to re-code those values to `NA`. How to do this is addressed in the [Import and export](importing.qmd) page. + +**To test whether a value is `NA`, use the special function `is.na()`**, which returns `TRUE` or `FALSE`. + + +```{r basics_operators_missing} +rdt_result <- c("Positive", "Suspected", "Positive", NA) # two positive cases, one suspected, and one unknown +is.na(rdt_result) # Tests whether the value of rdt_result is NA +``` + + +Read more about missing, infinite, `NULL`, and impossible values in the page on [Missing data](missing_data.qmd). Learn how to convert missing values when importing data in the page on [Import and export](importing.qmd). + + + +### Mathematics and statistics {.unnumbered} + +All the operators and functions in this page are automatically available using **base** R. + +#### Mathematical operators {.unnumbered} + +These are often used to perform addition, division, to create new columns, etc. Below are common mathematical operators in R. Whether you put spaces around the operators is not important. + +| Purpose | Example in R | +|---------------------|--------------| +| addition | 2 + 3 | +| subtraction | 2 - 3 | +| multiplication | 2 \* 3 | +| division | 30 / 5 | +| exponent | 2\^3 | +| order of operations | ( ) | + +#### Mathematical functions {.unnumbered} + +| Purpose | Function | +|--------------------|---------------------------------------| +| rounding | round(x, digits = n) | +| rounding | janitor::round_half_up(x, digits = n) | +| ceiling (round up) | ceiling(x) | +| floor (round down) | floor(x) | +| absolute value | abs(x) | +| square root | sqrt(x) | +| exponent | exponent(x) | +| natural logarithm | log(x) | +| log base 10 | log10(x) | +| log base 2 | log2(x) | + +Note: for `round()` the `digits =` specifies the number of decimal placed. Use `signif()` to round to a number of significant figures. + +#### Scientific notation {.unnumbered} + +The likelihood of scientific notation being used depends on the value of the `scipen` option. + +From the documentation of `?options`: scipen is a penalty to be applied when deciding to print numeric values in fixed or exponential notation. Positive values bias towards fixed and negative towards scientific notation: fixed notation will be preferred unless it is more than 'scipen' digits wider. + +If it is set to a low number (e.g. 0) it will be "turned on" always. To "turn off" scientific notation in your R session, set it to a very high number, for example: + + +```{r, eval=F} +# turn off scientific notation +options(scipen = 999) +``` + + +#### Rounding {.unnumbered} + +[***DANGER:*** `round()` uses "banker's rounding" which rounds up from a .5 only if the upper number is even. Use `round_half_up()` from **janitor** to consistently round halves up to the nearest whole number. See [this explanation](https://cran.r-project.org/web/packages/janitor/vignettes/janitor.html#explore-records-with-duplicated-values-for-specific-combinations-of-variables-with-get_dupes) ]{style="color: red;"} + + +```{r} +# use the appropriate rounding function for your work +round(c(2.5, 3.5)) + +janitor::round_half_up(c(2.5, 3.5)) +``` + + +#### Statistical functions {.unnumbered} + +[***CAUTION:*** The functions below will by default include missing values in calculations. Missing values will result in an output of `NA`, unless the argument `na.rm = TRUE` is specified. This can be written shorthand as `na.rm = T`.]{style="color: orange;"} + +| Objective | Function | +|-------------------------|----------------------| +| mean (average) | mean(x, na.rm = T) | +| median | median(x, na.rm= T) | +| standard deviation | sd(x, na.rm = T) | +| quantiles\* | quantile(x, probs) | +| sum | sum(x, na.rm = T) | +| minimum value | min(x, na.rm = T) | +| maximum value | max(x, na.rm = T) | +| range of numeric values | range(x, na.rm = T) | +| summary\*\* | summary(x) | + +Notes: + +- `*quantile()`: `x` is the numeric vector to examine, and `probs =` is a numeric vector with probabilities within 0 and 1.0, e.g `c(0.5, 0.8, 0.85)`. +- `**summary()`: gives a summary on a numeric vector including mean, median, and common percentiles. + +[***DANGER:*** If providing a vector of numbers to one of the above functions, be sure to wrap the numbers within `c()` .]{style="color: red;"} + + +```{r} +# If supplying raw numbers to a function, wrap them in c() +mean(1, 6, 12, 10, 5, 0) # !!! INCORRECT !!! + +mean(c(1, 6, 12, 10, 5, 0)) # CORRECT +``` + + +#### Other useful functions {.unnumbered} + ++----------------------------+-------------------+-------------------------------------------------+ +| Objective | Function | Example | ++============================+===================+=================================================+ +| create a sequence | seq(from, to, by) | `seq(1, 10, 2)` | ++----------------------------+-------------------+-------------------------------------------------+ +| repeat x, n times | rep(x, ntimes) | `rep(1:3, 2)` or `rep(c("a", "b", "c"), 3)` | ++----------------------------+-------------------+-------------------------------------------------+ +| subdivide a numeric vector | cut(x, n) | `cut(linelist$age, 5)` | ++----------------------------+-------------------+-------------------------------------------------+ +| take a random sample | sample(x, size) | `sample(linelist$id, size = 5, replace = TRUE)` | ++----------------------------+-------------------+-------------------------------------------------+ + + + +### `%in%` {.unnumbered} + +A very useful operator for matching values, and for quickly assessing if a value is within a vector or data frame. + + +```{r} +my_vector <- c("a", "b", "c", "d") +``` + +```{r} +"a" %in% my_vector +"h" %in% my_vector +``` + + +To ask if a value is **not** `%in%` a vector, put an exclamation mark (!) **in front** of the logic statement: + + +```{r} +# to negate, put an exclamation in front +!"a" %in% my_vector +!"h" %in% my_vector +``` + + +`%in%` is very useful when using the **dplyr** function `case_when()`. You can define a vector previously, and then reference it later. For example: + + +```{r eval=F} +affirmative <- c("1", "Yes", "YES", "yes", "y", "Y", "oui", "Oui", "Si") + +linelist <- linelist %>% + mutate(child_hospitaled = case_when( + hospitalized %in% affirmative & age < 18 ~ "Hospitalized Child", + TRUE ~ "Not")) +``` + + +Note: If you want to detect a partial string, perhaps using `str_detect()` from **stringr**, it will not accept a character vector like `c("1", "Yes", "yes", "y")`. Instead, it must be given a *regular expression* - one condensed string with OR bars, such as "1\|Yes\|yes\|y". For example, `str_detect(hospitalized, "1|Yes|yes|y")`. See the page on [Characters and strings](characters_strings.qmd) for more information. + +You can convert a character vector to a named regular expression with this command: + + +```{r} +affirmative <- c("1", "Yes", "YES", "yes", "y", "Y", "oui", "Oui", "Si") +affirmative + +# condense to +affirmative_str_search <- paste0(affirmative, collapse = "|") # option with base R +affirmative_str_search <- str_c(affirmative, collapse = "|") # option with stringr package + +affirmative_str_search +``` + + + + + + + + +## Errors & warnings + +This section explains: + +- The difference between errors and warnings.\ +- General syntax tips for writing R code.\ +- Code assists. + +Common errors and warnings and troubleshooting tips can be found in the page on [Errors and help](errors.qmd). + + + +### Error versus Warning {.unnumbered} + +When a command is run, the R Console may show you warning or error messages in red text. + +- A **warning** means that R has completed your command, but had to take additional steps or produced unusual output that you should be aware of. + +- An **error** means that R was not able to complete your command. + +Look for clues: + +- The error/warning message will often include a line number for the problem. + +- If an object "is unknown" or "not found", perhaps you spelled it incorrectly, forgot to call a package with library(), or forgot to re-run your script after making changes. + +If all else fails, copy the error message into Google along with some key terms - chances are that someone else has worked through this already! + + + +### General syntax tips {.unnumbered} + +A few things to remember when writing commands in R, to avoid errors and warnings: + +- Always close parentheses - tip: count the number of opening "(" and closing parentheses ")" for each code chunk. +- Avoid spaces in column and object names. Use underscore ( \_ ) or periods ( . ) instead. +- Keep track of and remember to separate a function's arguments with commas. +- R is case-sensitive, meaning `Variable_A` is *different* from `variable_A`. + + + +### Code assists {.unnumbered} + +Any script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the left hand side of the script, to warn you. + diff --git a/new_pages/basics_files/execute-results/html.json b/new_pages/basics_files/execute-results/html.json new file mode 100644 index 00000000..d1967d84 --- /dev/null +++ b/new_pages/basics_files/execute-results/html.json @@ -0,0 +1,17 @@ +{ + "hash": "0f445c9bd187b8df9c852d072f545106", + "result": { + "engine": "knitr", + "markdown": "# R Basics {#rbasics}\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/basics_header_close.png){fig-align='center' width=100%}\n:::\n:::\n\n\nWelcome!\n\nThis page reviews the essentials of R. It is not intended to be a comprehensive tutorial, but it provides the basics and can be useful for refreshing your memory. The section on [Resources for learning](#learning) links to more comprehensive tutorials.\n\nParts of this page have been adapted with permission from the [R4Epis project](https://r4epis.netlify.app/).\n\nSee the page on [Transition to R](transition_to_R.qmd) for tips on switching to R from STATA, SAS, or Excel.\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: The `trust` argument of `import()` should be explicit for serialization formats\nas of rio 1.0.3.\nℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\nℹ The deprecated feature was likely used in the rio package.\n Please report the issue at .\n```\n\n\n:::\n:::\n\n\n\n\n## Why use R?\n\nAs stated on the [R project website](https://www.r-project.org/about.html), R is a programming language and environment for statistical computing and graphics. It is highly versatile, extendable, and community-driven.\n\n**Cost**\n\nR is free to use! There is a strong ethic in the community of free and open-source material.\n\n**Reproducibility**\n\nConducting your data management and analysis through a programming language (compared to Excel or another primarily point-click/manual tool) enhances **reproducibility**, makes **error-detection** easier, and eases your workload.\n\n**Community**\n\nThe R community of users is enormous and collaborative. New packages and tools to address real-life problems are developed daily, and vetted by the community of users. As one example, [R-Ladies](https://rladies.org/) is a worldwide organization whose mission is to promote gender diversity in the R community, and is one of the largest organizations of R users. It likely has a chapter near you!\n\n## Key terms\n\n**RStudio** - RStudio is a Graphical User Interface (GUI) for easier use of **R**. Read more [in the RStudio section](#rstudio).\n\n**Objects** - Everything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are *objects* which are *assigned a name* and *can be referenced* in later commands. Read more in the [Objects section](#objects).\n\n**Functions** - A function is a code operation that accept inputs and returns a transformed output. Read more [in the Functions section](#functions).\n\n**Packages** - An R package is a shareable bundle of functions. Read more [in the Packages section](#packages).\n\n**Scripts** - A script is the document file that hold your commands. Read more [in the Scripts section](#scripts)\n\n## Resources for learning {#learning}\n\n### Resources within RStudio {.unnumbered}\n\n**Help documentation**\n\nSearch the RStudio \"Help\" tab for documentation on R packages and specific functions. This is within the pane that also contains Files, Plots, and Packages (typically in the lower-right pane). As a shortcut, you can also type the name of a package or function into the R console after a question-mark to open the relevant Help page. Do not include parentheses.\n\nFor example: `?filter` or `?diagrammeR`.\n\n**Interactive tutorials**\n\nThere are several ways to learn R interactively *within* RStudio.\n\nRStudio itself offers a Tutorial pane that is powered by the [**learnr**](https://blog.rstudio.com/2020/02/25/rstudio-1-3-integrated-tutorials/) R package. Simply install this package and open a tutorial via the new \"Tutorial\" tab in the upper-right RStudio pane (which also contains Environment and History tabs).\n\nThe R package [**swirl**](https://swirlstats.com/) offers interactive courses in the R Console. Install and load this package, then run the command `swirl()` (empty parentheses) in the R console. You will see prompts appear in the Console. Respond by typing in the Console. It will guide you through a course of your choice.\n\n### Cheatsheets {.unnumbered}\n\nThere are many PDF \"cheatsheets\" available on the [RStudio website](https://rstudio.com/resources/cheatsheets/), for example:\n\n- Factors with **forcats** package.\\\n- Dates and times with **lubridate** package.\\\n- Strings with **stringr** package.\\\n- iterative opertaions with **purrr** package.\\\n- Data import.\\\n- Data transformation cheatsheet with **dplyr** package.\\\n- R Markdown (to create documents like PDF, Word, Powerpoint...).\\\n- Shiny (to build interactive web apps).\\\n- Data visualization with **ggplot2** package.\\\n- Cartography (GIS).\\\n- **leaflet** package (interactive maps).\\\n- Python with R (**reticulate** package).\n\nThis is an online R resource specifically for [Excel users](https://jules32.github.io/r-for-excel-users/).\n\n### Twitter {.unnumbered}\n\n- Follow us! [\\@epiRhandbook](https://twitter.com/epirhandbook)\n\nAlso:\n\n**\\#epitwitter** and **\\#rstats**\n\n### Free online resources {.unnumbered}\n\nA definitive text is the [R for Data Science](https://r4ds.hadley.nz/) book by Garrett Grolemund and Hadley Wickham.\n\nThe [R4Epis](https://r4epis.netlify.app/) project website aims to \"develop standardised data cleaning, analysis and reporting tools to cover common types of outbreaks and population-based surveys that would be conducted in an MSF emergency response setting\". You can find R basics training materials, templates for RMarkdown reports on outbreaks and surveys, and tutorials to help you set them up.\n\n### Languages other than English {.unnumbered}\n\n[Materiales de RStudio en Español](https://www.rstudio.com/collections/espanol/)\n\n[Introduction à R et au tidyverse (Francais)](https://juba.github.io/tidyverse/index.html)\n\n\n\n## Installation\n\n### R and RStudio {.unnumbered}\n\n**How to install R**\n\nVisit this website and download the latest version of R suitable for your computer.\n\n**How to install RStudio**\n\nVisit this website and download the latest free Desktop version of RStudio suitable for your computer.\n\n**Permissions**\\\nNote that you should install R and RStudio to a drive where you have read and write permissions. Otherwise, your ability to install R packages (a frequent occurrence) will be impacted. If you encounter problems, try opening RStudio by right-clicking the icon and selecting \"Run as administrator\". Other tips can be found in the page [R on network drives](network_drives.qmd).\n\n**How to update R and RStudio**\n\nYour version of R is printed to the R Console at start-up. You can also run `sessionInfo()`.\n\nTo update R, go to the website mentioned above and re-install R. Alternatively, you can use the **installr** package (on Windows) by running `installr::updateR()`. This will open dialog boxes to help you download the latest R version and update your packages to the new R version. More details can be found in the **installr** [documentation](https://talgalili.github.io/installr/).\n\nBe aware that the old R version will still exist in your computer. You can temporarily run an older version of R by clicking \"Tools\" -\\> \"Global Options\" in RStudio and choosing an R version. This can be useful if you want to use a package that has not been updated to work on the newest version of R.\n\nTo update RStudio, you can go to the website above and re-download RStudio. Another option is to click \"Help\" -\\> \"Check for Updates\" within RStudio, but this may not show the very latest updates.\n\nTo see which versions of R, RStudio, or packages were used when this Handbook as made, see the page on [Editorial and technical notes](editorial_style.qmd).\n\n### Other software you *may* need to install {.unnumbered}\n\n- TinyTeX (for compiling an RMarkdown document to PDF).\\\n- Pandoc (for compiling RMarkdown documents).\\\n- RTools (for building packages for R).\\\n- phantomjs (for saving still images of animated networks, such as transmission chains).\n\n#### TinyTex {.unnumbered}\n\nTinyTex is a custom LaTeX distribution, useful when trying to produce PDFs from R.\\\nSee for more informaton.\n\nTo install TinyTex from R:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages('tinytex')\ntinytex::install_tinytex()\n# to uninstall TinyTeX, run tinytex::uninstall_tinytex()\n```\n:::\n\n\n#### Pandoc {.unnumbered}\n\nPandoc is a document converter, a separate software from R. **It comes bundled with RStudio and should not need to be downloaded.** It helps the process of converting Rmarkdown documents to formats like .pdf and adding complex functionality.\n\n#### RTools {.unnumbered}\n\nRTools is a collection of software for building packages for R\n\nInstall from this website: \n\n#### phantomjs {.unnumbered}\n\nThis is often used to take \"screenshots\" of webpages. For example when you make a transmission chain with **epicontacts** package, an HTML file is produced that is interactive and dynamic. If you want a static image, it can be useful to use the [**webshot**](https://wch.github.io/webshot/articles/intro.html) package to automate this process. This will require the external program \"phantomjs\". You can install phantomjs via the **webshot** package with the command `webshot::install_phantomjs()`.\n\n\n\n## RStudio {#rstudio}\n\n### RStudio orientation {.unnumbered}\n\n**First, open RStudio.** \n\nAs their icons can look very similar, be sure you are opening *RStudio* and not R.\n\nFor RStudio to work you must also have R installed on the computer (see above for installation instructions).\n\n**RStudio** is an interface (GUI) for easier use of **R**. You can think of R as being the engine of a vehicle, doing the crucial work, and RStudio as the body of the vehicle (with seats, accessories, etc.) that helps you actually use the engine to move forward! You can see the complete RStudio user-interface cheatsheet (PDF) [here](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html).\n\nBy default RStudio displays four rectangle panes.\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/RStudio_overview.png){fig-align='center' width=100%}\n:::\n:::\n\n\n[***TIP:*** If your RStudio displays only one left pane it is because you have no scripts open yet.]{style=\"color: black;\"}\n\n**The Source Pane**\\\nThis pane, by default in the upper-left, is a space to edit, run, and save your [scripts](#scripts). Scripts contain the commands you want to run. This pane can also display datasets (data frames) for viewing.\n\nFor Stata users, this pane is similar to your Do-file and Data Editor windows.\n\n**The R Console Pane**\n\nThe R Console, by default the left or lower-left pane in R Studio, is the home of the R \"engine\". This is where the commands are actually run and non-graphic outputs and error/warning messages appear. You can directly enter and run commands in the R Console, but realize that these commands are not saved as they are when running commands from a script.\n\nIf you are familiar with Stata, the R Console is like the Command Window and also the Results Window.\n\n**The Environment Pane**\\\nThis pane, by default in the upper-right, is most often used to see brief summaries of [objects](#objects) in the R Environment in the current session. These objects could include imported, modified, or created datasets, parameters you have defined (e.g. a specific epi week for the analysis), or vectors or lists you have defined during analysis (e.g. names of regions). You can click on the arrow next to a data frame name to see its variables.\n\nIn Stata, this is most similar to the Variables Manager window.\n\nThis pane also contains *History* where you can see commands that you can previously. It also has a \"Tutorial\" tab where you can complete interactive R tutorials if you have the **learnr** package installed. It also has a \"Connections\" pane for external connections, and can have a \"Git\" pane if you choose to interface with Github.\n\n**Plots, Viewer, Packages, and Help Pane**\\\nThe lower-right pane includes several important tabs. Typical plot graphics including maps will display in the Plot pane. Interactive or HTML outputs will display in the Viewer pane. The Help pane can display documentation and help files. The Files pane is a browser which can be used to open or delete files. The Packages pane allows you to see, install, update, delete, load/unload R packages, and see which version of the package you have. To learn more about packages see the [packages section](#packages) below.\n\nThis pane contains the Stata equivalents of the Plots Manager and Project Manager windows.\n\n### RStudio settings {.unnumbered}\n\nChange RStudio settings and appearance in the *Tools* drop-down menu, by selecting *Global Options*. There you can change the default settings, including appearance/background color.\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](../images/RStudio_tools_options_1.png){width=50%}\n:::\n\n::: {.cell-output-display}\n![](../images/RStudio_tools_options.png){width=50%}\n:::\n:::\n\n\n**Restart**\n\nIf your R freezes, you can re-start R by going to the Session menu and clicking \"Restart R\". This avoids the hassle of closing and opening RStudio. \n\n\n**_CAUTION:_** Everything in your R environment will be removed when you do this.\n\n### Keyboard shortcuts {.unnumbered}\n\nSome very useful keyboard shortcuts are below. See all the keyboard shortcuts for Windows, Max, and Linux [Rstudio user interface cheatsheet](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html#keyboard-shortcuts).\n\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Windows/Linux | Mac | Action |\n+==================================+========================+================================================================================================================================+\n| Esc | Esc | Interrupt current command (useful if you accidentally ran an incomplete command and cannot escape seeing \"+\" in the R console). |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl+s | Cmd+s | Save (script). |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Tab | Tab | Auto-complete. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Enter | Cmd + Enter | Run current line(s)/selection of code. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Shift + C | Cmd + Shift + c | Comment/uncomment the highlighted lines. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Alt + - | Option + - | Insert `<-`. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Shift + m | Cmd + Shift + m | Insert `%>%`. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + l | Cmd + l | Clear the R console. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Alt + b | Cmd + Option + b | Run from start to current. line |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Alt + t | Cmd + Option + t | Run the current code section (R Markdown). |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Alt + i | Cmd + Shift + r | Insert code chunk (into R Markdown). |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Alt + c | Cmd + Option + c | Run current code chunk (R Markdown). |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| up/down arrows in R console | Same | Toggle through recently run commands. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Shift + up/down arrows in script | Same | Select multiple code lines. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + f | Cmd + f | Find and replace in current script. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Ctrl + Shift + f | Cmd + Shift + f | Find in files (search/replace across many scripts). |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Alt + l | Cmd + Option + l | Fold selected code. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n| Shift + Alt + l | Cmd + Shift + Option+l | Unfold selected code. |\n+----------------------------------+------------------------+--------------------------------------------------------------------------------------------------------------------------------+\n\n[***TIP:*** Use your Tab key when typing to engage RStudio's auto-complete functionality. This can prevent spelling errors. Press Tab while typing to produce a drop-down menu of likely functions and objects, based on what you have typed so far.]{style=\"color: darkgreen;\"}\n\n\n\n## Functions {#functions}\n\nFunctions are at the core of using R. Functions are how you perform tasks and operations. Many functions come installed with R, many more are available for download in *packages* (explained in the [packages](#packages) section), and you can even write your own custom functions!\n\nThis basics section on functions explains:\n\n- What a function is and how they work.\\\n- What function *arguments* are.\\\n- How to get help understanding a function.\n\n*A quick note on syntax:* In this handbook, functions are written in code-text with open parentheses, like this: `filter()`. As explained in the [packages](#packages) section, functions are downloaded within *packages*. In this handbook, package names are written in **bold**, like **dplyr**. Sometimes in example code you may see the function name linked explicitly to the name of its package with two colons (`::`) like this: `dplyr::filter()`. The purpose of this linkage is explained in the packages section.\n\n\n\n### Simple functions {.unnumbered}\n\n**A function is like a machine that receives inputs, carries out an action with those inputs, and produces an output.** What the output is depends on the function.\n\n**Functions typically operate upon some object placed within the function's parentheses**. For example, the function `sqrt()` calculates the square root of a number:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt(49)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 7\n```\n\n\n:::\n:::\n\n\nThe object provided to a function also can be a column in a dataset (see the [Objects](#objects) section for detail on all the kinds of objects). Because R can store multiple datasets, you will need to specify both the dataset and the column. One way to do this is using the `$` notation to link the name of the dataset and the name of the column (`dataset$column`). In the example below, the function `summary()` is applied to the numeric column `age` in the dataset `linelist`, and the output is a summary of the column's numeric and missing values.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Print summary statistics of column 'age' in the dataset 'linelist'\nsummary(linelist$age)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.07 23.00 84.00 86 \n```\n\n\n:::\n:::\n\n\n[***NOTE:*** Behind the scenes, a function represents complex additional code that has been wrapped up for the user into one easy command.]{style=\"color: black;\"}\n\n\n\n### Functions with multiple arguments {.unnumbered}\n\nFunctions often ask for several inputs, called ***arguments***, located within the parentheses of the function, usually separated by commas.\n\n- Some arguments are required for the function to work correctly, others are optional.\\\n- Optional arguments have default settings.\\\n- Arguments can take character, numeric, logical (TRUE/FALSE), and other inputs.\n\nHere is a fun fictional function, called `oven_bake()`, as an example of a typical function. It takes an input object (e.g. a dataset, or in this example \"dough\") and performs operations on it as specified by additional arguments (`minutes =` and `temperature =`). The output can be printed to the console, or saved as an object using the assignment operator `<-`.\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/Function_Bread_Example.png){fig-align='center' width=75%}\n:::\n:::\n\n\n**In a more realistic example**, the `age_pyramid()` command below produces an age pyramid plot based on defined age groups and a binary split column, such as `gender`. The function is given three arguments within the parentheses, separated by commas. The values supplied to the arguments establish `linelist` as the data frame to use, `age_cat5` as the column to count, and `gender` as the binary column to use for splitting the pyramid by color.\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Create an age pyramid\nage_pyramid(data = linelist, age_group = \"age_cat5\", split_by = \"gender\")\n```\n\n::: {.cell-output-display}\n![](basics_files/figure-html/unnamed-chunk-6-1.png){width=75% height=75%}\n:::\n:::\n\n\nThe above command can be equivalently written as below, in a longer style with a new line for each argument. This style can be easier to read, and easier to write \"comments\" with `#` to explain each part (commenting extensively is good practice!). To run this longer command you can highlight the entire command and click \"Run\", or just place your cursor in the first line and then press the Ctrl and Enter keys simultaneously.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Create an age pyramid\nage_pyramid(\n data = linelist, # use case linelist\n age_group = \"age_cat5\", # provide age group column\n split_by = \"gender\" # use gender column for two sides of pyramid\n )\n```\n\n::: {.cell-output-display}\n![](basics_files/figure-html/unnamed-chunk-7-1.png){width=75% height=75%}\n:::\n:::\n\n\nThe first half of an argument assignment (e.g. `data =`) does not need to be specified if the arguments are written in a specific order (specified in the function's documentation). The below code produces the exact same pyramid as above, because the function expects the argument order: data frame, `age_group` variable, `split_by` variable.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# This command will produce the exact same graphic as above\nage_pyramid(linelist, \"age_cat5\", \"gender\")\n```\n:::\n\n\n**A more complex `age_pyramid()` command might include the *optional* arguments to:**\n\n- Show proportions instead of counts (set `proportional = TRUE` when the default is `FALSE`)\\\n- Specify the two colors to use (`pal =` is short for \"palette\" and is supplied with a vector of two color names. See the [objects](https://epirhandbook.com/new_pages/basics.html#objects) page for how the function `c()` makes a vector)\n\n[***NOTE:*** For arguments that you specify with both parts of the argument (e.g. `proportional = TRUE`), their order among all the arguments does not matter.]{style=\"color: black;\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_pyramid(\n linelist, # use case linelist\n \"age_cat5\", # age group column\n \"gender\", # split by gender\n proportional = TRUE, # percents instead of counts\n pal = c(\"orange\", \"purple\") # colors\n )\n```\n\n::: {.cell-output-display}\n![](basics_files/figure-html/unnamed-chunk-8-1.png){width=75% height=75%}\n:::\n:::\n\n***TIP:*** Remember that you can put `?` before a function to see what arguments the function can take, and which arguments are needed and which arguments have default values. For example `?age_pyramid'.\n\n\n\n### Writing Functions {.unnumbered}\n\nR is a language that is oriented around functions, so you should feel empowered to write your own functions. Creating functions brings several advantages:\n\n- To facilitate modular programming - the separation of code in to independent and manageable pieces.\\\n- Replace repetitive copy-and-paste, which can be error prone.\\\n- Give pieces of code memorable names.\n\nHow to write a function is covered in-depth in the [Writing functions](writing_functions.qmd) page.\n\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n:::\n\n\n\n\n\n\n\n\n## Packages {#packages}\n\n**Packages contain functions.**\n\nAn R package is a shareable bundle of code and documentation that contains pre-defined functions. Users in the R community develop packages all the time catered to specific problems, it is likely that one can help with your work! You will install and use hundreds of packages in your use of R.\n\nOn installation, R contains **\"base\"** packages and functions that perform common elementary tasks. But many R users create specialized functions, which are verified by the R community and which you can download as a **package** for your own use. In this handbook, package names are written in **bold**. One of the more challenging aspects of R is that there are often many functions or packages to choose from to complete a given task.\n\n### Install and load {.unnumbered}\n\n*Functions* are contained within **packages** which can be downloaded (\"installed\") to your computer from the internet. Once a package is downloaded, it is stored in your \"library\". You can then access the functions it contains during your current R session by \"loading\" the package.\n\n*Think of R as your personal library*: When you download a package, your library gains a new book of functions, but each time you want to use a function in that book, you must borrow,\"load\", that book from your library.\n\nIn summary: to use the functions available in an R package, 2 steps must be implemented:\n\n1) The package must be **installed** (once), *and*\\\n2) The package must be **loaded** (each R session)\n\n#### Your library {.unnumbered}\n\nYour \"library\" is actually a folder on your computer, containing a folder for each package that has been installed. Find out where R is installed in your computer, and look for a folder called \"win-library\". For example: `R\\win-library\\4.4.1` (the 4.4.1 is the R version - you'll have a different library for each R version you've downloaded).\n\nYou can print the file path to your library by entering `.libPaths()` (empty parentheses). This becomes especially important if working with [R on network drives](network_drives.qmd).\n\n#### Install from CRAN {.unnumbered}\n\nMost often, R users download packages from CRAN. CRAN (Comprehensive R Archive Network) is an online public warehouse of R packages that have been published by R community members.\n\nAre you worried about viruses and security when downloading a package from CRAN? Read [this article](https://support.rstudio.com/hc/en-us/articles/360042593974-R-and-R-Package-Security) on the topic.\n\n#### How to install and load {.unnumbered}\n\nIn this handbook, we suggest using the **pacman** package (short for \"package manager\"). It offers a convenient function `p_load()` which will install a package if necessary *and* load it for use in the current R session.\n\nThe syntax quite simple. Just list the names of the packages within the `p_load()` parentheses, separated by commas. This command will install the **rio**, **tidyverse**, and **here** packages if they are not yet installed, and will load them for use. This makes the `p_load()` approach convenient and concise if sharing scripts with others. \n\nNote that package names are case-sensitive.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Install (if necessary) and load packages for use\npacman::p_load(rio, tidyverse, here)\n```\n:::\n\n\nHere we have used the syntax `pacman::p_load()` which explicitly writes the package name (**pacman**) prior to the function name (`p_load()`), connected by two colons `::`. This syntax is useful because it also loads the **pacman** package (assuming it is already installed).\n\nThere are alternative **base** R functions that you will see often. The **base** R function for installing a package is `install.packages()`. The name of the package to install must be provided in the parentheses *in quotes*. If you want to install multiple packages in one command, they must be listed within a character vector `c()`.\n\nNote: this command *installs* a package, but does *not* load it for use in the current session.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# install a single package with base R\ninstall.packages(\"tidyverse\")\n\n# install multiple packages with base R\ninstall.packages(c(\"tidyverse\", \"rio\", \"here\"))\n```\n:::\n\n\nInstallation can also be accomplished point-and-click by going to the RStudio \"Packages\" pane and clicking \"Install\" and searching for the desired package name.\n\nThe **base** R function to **load** a package for use (after it has been installed) is `library()`. It can load only one package at a time (another reason to use `p_load()`). You can provide the package name with or without quotes.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# load packages for use, with base R\nlibrary(tidyverse)\nlibrary(rio)\nlibrary(here)\n```\n:::\n\n\nTo check whether a package in installed or loaded, you can view the Packages pane in RStudio. If the package is installed, it is shown there with version number. If its box is checked, it is loaded for the current session.\n\n**Install from Github**\n\nSometimes, you need to install a package that is not yet available from CRAN. Or perhaps the package is available on CRAN but you want the *development version* with new features not yet offered in the more stable published CRAN version. These are often hosted on the website [github.com](https://github.com/) in a free, public-facing code \"repository\". Read more about Github in the handbook page on [Version control and collaboration with Git and Github](https://epirhandbook.com/new_pages/collaboration.html).\n\nTo download R packages from Github, you can use the function `p_load_gh()` from **pacman**, which will install the package if necessary, and load it for use in your current R session. Alternatives to install include using the **remotes** or **devtools** packages. Read more about all the **pacman** functions in the [package documentation](https://cran.r-project.org/web/packages/pacman/pacman.pdf).\n\nTo install from Github, you have to provide more information. You must provide:\n\n1) The Github ID of the repository owner\n2) The name of the repository that contains the package\\\n3) Optional: *The name of the \"branch\" (specific development version) you want to download*\n\nIn the examples below, the first word in the quotation marks is the Github ID of the repository owner, after the slash is the name of the repository (the name of the package).\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# install/load the epicontacts package from its Github repository\np_load_gh(\"reconhub/epicontacts\")\n```\n:::\n\n\nIf you want to install from a \"branch\" (version) other than the main branch, add the branch name after an \"\\@\", after the repository name.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# install the \"timeline\" branch of the epicontacts package from Github\np_load_gh(\"reconhub/epicontacts@timeline\")\n```\n:::\n\n\nIf there is no difference between the Github version and the version on your computer, no action will be taken. You can \"force\" a re-install by instead using `p_load_current_gh()` with the argument `update = TRUE`. Read more about **pacman** in this [online vignette](http://trinker.github.io/pacman/vignettes/Introduction_to_pacman.html)\n\n**Install from ZIP or TAR**\n\nYou could install the package from a URL:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npackageurl <- \"https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz\"\ninstall.packages(packageurl, repos=NULL, type=\"source\")\n```\n:::\n\n\nOr, download it to your computer in a zipped file:\n\nOption 1: using `install_local()` from the **remotes** package\n\n\n::: {.cell}\n\n```{.r .cell-code}\nremotes::install_local(\"~/Downloads/dplyr-master.zip\")\n```\n:::\n\n\nOption 2: using `install.packages()` from **base** R, providing the file path to the ZIP file and setting `type = \"source` and `repos = NULL`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"~/Downloads/dplyr-master.zip\", repos=NULL, type=\"source\")\n```\n:::\n\n\n### Code syntax {.unnumbered}\n\nFor clarity in this handbook, functions are sometimes preceded by the name of their package using the `::` symbol in the following way: `package_name::function_name()`\n\nOnce a package is loaded for a session, this explicit style is not necessary. One can just use `function_name()`. However writing the package name is useful when a function name is common and may exist in multiple packages (e.g. `plot()`). Writing the package name will also load the package if it is not already loaded.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# This command uses the package \"rio\" and its function \"import()\" to import a dataset\nlinelist <- rio::import(\"linelist.xlsx\", which = \"Sheet1\")\n```\n:::\n\n\n### Function help {.unnumbered}\n\nTo read more about a function, you can search for it in the Help tab of the lower-right RStudio. You can also run a command like `?thefunctionname` (for example, to get help for the function `p_load` you would write `?p_load`) and the Help page will appear in the Help pane. Finally, try searching online for resources.\n\n### Update packages {.unnumbered}\n\nYou can update packages by re-installing them. You can also click the green \"Update\" button in your RStudio Packages pane to see which packages have new versions to install. Be aware that your old code may need to be updated if there is a major revision to how a function works!\n\n### Delete packages {.unnumbered}\n\nUse `p_delete()` from **pacman**, or `remove.packages()` from **base** R.\n\n### Dependencies {.unnumbered}\n\nPackages often depend on other packages to work. These are called dependencies. If a dependency fails to install, then the package depending on it may also fail to install.\n\nSee the dependencies of a package with `p_depends()`, and see which packages depend on it with `p_depends_reverse()`\n\n### Masked functions {.unnumbered}\n\nIt is not uncommon that two or more packages contain the same function name. For example, the package **dplyr** has a `filter()` function, but so does the package **stats**. The default `filter()` function depends on the order these packages are first loaded in the R session - the later one will be the default for the command `filter()`.\n\nYou can check the order in your Environment pane of R Studio - click the drop-down for \"Global Environment\" and see the order of the packages. Functions from packages *lower* on that drop-down list will mask functions of the same name in packages that appear higher in the drop-down list. When first loading a package, R will warn you in the console if masking is occurring, but this can be easy to miss.\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/masking_functions.png){fig-align='center' width=50%}\n:::\n:::\n\n\nHere are ways you can fix masking:\n\n1) Specify the package name in the command. For example, use `dplyr::filter()`\\\n2) Re-arrange the order in which the packages are loaded (e.g. within `p_load()`), and **start a new R session**\n\n### Detach / unload {.unnumbered}\n\nTo detach (unload) a package, use this command, with the correct package name and only one colon. Note that this may not resolve masking.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndetach(package:PACKAGE_NAME_HERE, unload=TRUE)\n```\n:::\n\n\n### Install older version {.unnumbered}\n\nSee this [guide](https://support.rstudio.com/hc/en-us/articles/219949047-Installing-older-versions-of-packages) to install an older version of a particular package.\n\n### Suggested packages {.unnumbered}\n\nSee the page on [Suggested packages](packages_suggested.qmd) for a listing of packages we recommend for everyday epidemiology.\n\n\n\n## Scripts {#scripts}\n\nScripts are a fundamental part of programming. They are documents that hold your commands (e.g. functions to create and modify datasets, print visualizations, etc). You can save a script and run it again later. There are many advantages to storing and running your commands from a script (vs. typing commands one-by-one into the R console \"command line\"):\n\n- Portability - you can share your work with others by sending them your scripts.\\\n- Reproducibility - so that you and others know exactly what you did.\\\n- Version control - so you can track changes made by yourself or colleagues.\\\n- Commenting/annotation - to explain to your colleagues what you have done.\n\n### Commenting {.unnumbered}\n\nIn a script you can also annotate (\"comment\") around your R code. Commenting is helpful to explain to yourself and other readers what you are doing. You can add a comment by typing the hash symbol (\\#) and writing your comment after it. The commented text will appear in a different color than the R code.\n\nAny code written after the \\# will not be run. Therefore, placing a \\# before code is also a useful way to temporarily block a line of code (\"comment out\") if you do not want to delete it. You can comment out/in multiple lines at once by highlighting them and pressing Ctrl+Shift+c (Cmd+Shift+c in Mac).\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# A comment can be on a line by itself\n# import data\nlinelist <- import(\"linelist_raw.xlsx\") %>% # a comment can also come after code\n# filter(age > 50) # It can also be used to deactivate / remove a line of code\n count()\n```\n:::\n\n\nThere are a few general ideas to follow when writing your scripts in order to make them accessible.\n- Add comments on *what* you are doing *and on **why** you are doing it*.\\\n- Break your code into logical sections.\\\n- Accompany your code with a text step-by-step description of what you are doing (e.g. numbered steps).\n\n### Style {.unnumbered}\n\nIt is important to be conscious of your coding style - especially if working on a team. We advocate for the **tidyverse** [style guide](https://style.tidyverse.org/). There are also packages such as **styler** and **lintr** which help you conform to this style.\n\nA few very basic points to make your code readable to others:\\\n\\* When naming objects, use only lowercase letters, numbers, and underscores `_`, e.g. `my_data`\\\n\\* Use frequent spaces, including around operators, e.g. `n = 1` and `age_new <- age_old + 3`\n\n### Example Script {.unnumbered}\n\nBelow is an example of a short R script. Remember, the better you succinctly explain your code in comments, the more your colleagues will like you!\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/example_script.png){fig-align='center' width=100%}\n:::\n:::\n\n\n\n\n### R markdown and Quarto {.unnumbered}\n\nAn [R Markdown](https://rmarkdown.rstudio.com/) or [Quarto](https://quarto.org/docs/computations/r.html) script are types of R script in which the script itself *becomes* an output document (PDF, Word, HTML, Powerpoint, etc.). These are incredibly useful and versatile tools often used to create dynamic and automated reports. \n\nEven this website and handbook is produced with Quarto scripts!\n\nIt is worth noting that beginner R users can also use R Markdown - do not be intimidated! To learn more, see the handbook page on [Reports with R Markdown](rmarkdown.qmd) documents.\n\n\n\n### R notebooks {.unnumbered}\n\nThere is no difference between writing in a Rmarkdown vs an R notebook. However the execution of the document differs slightly. See this [site](http://uc-r.github.io/r_notebook) for more details.\n\n\n\n### Shiny {.unnumbered}\n\nShiny apps/websites are contained within one script, which must be named `app.R`. This file has three components:\n\n1) A user interface (ui).\\\n2) A server function.\\\n3) A call to the `shinyApp` function.\n\nSee the handbook page on [Dashboards with Shiny](shiny_basics.qmd), or this online tutorial: [Shiny tutorial](https://shiny.rstudio.com/tutorial/written-tutorial/lesson1/)\n\n*In previous versions, the above file was split into two files (`ui.R` and `server.R`)*\n\n### Code folding {.unnumbered}\n\nYou can collapse portions of code to make your script easier to read.\n\nTo do this, create a text header with \\#, write your header, and follow it with at least 4 of either dashes (-), hashes (\\#) or equals (=). When you have done this, a small arrow will appear in the \"gutter\" to the left (by the row number). You can click this arrow and the code below until the next header will collapse and a dual-arrow icon will appear in its place.\n\nTo expand the code, either click the arrow in the gutter again, or the dual-arrow icon. There are also keyboard shortcuts as explained in the [RStudio section](#rstudio) of this page.\n\nBy creating headers with \\#, you will also activate the Table of Contents at the bottom of your script (see below) that you can use to navigate your script. You can create sub-headers by adding more \\# symbols, for example \\# for primary, \\#\\# for secondary, and \\#\\#\\# for tertiary headers.\n\nBelow are two versions of an example script. On the left is the original with commented headers. On the right, four dashes have been written after each header, making them collapsible. Two of them have been collapsed, and you can see that the Table of Contents at the bottom now shows each section.\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](../images/code_folding1.png){width=50%}\n:::\n\n::: {.cell-output-display}\n![](../images/code_folding2.png){width=50%}\n:::\n:::\n\n\nOther areas of code that are automatically eligible for folding include \"braced\" regions with brackets `{ }` such as function definitions or conditional blocks (if else statements). You can read more about code folding at the RStudio [site](https://support.rstudio.com/hc/en-us/articles/200484568-Code-Folding-and-Sections).\n\n\n\n\n\n\n\n## Working directory\n\nThe working directory is the root folder location used by R for your work - where R looks for and saves files by default. By default, it will save new files and outputs to this location, and will look for files to import (e.g. datasets) here as well.\n\nThe working directory appears in grey text at the top of the RStudio Console pane. You can also print the current working directory by running `getwd()` (leave the parentheses empty).\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/working_directory_1.png){fig-align='center' width=100%}\n:::\n:::\n\n\n### Recommended approach {.unnumbered}\n\n**See the page on [R projects](r_projects.qmd) for details on our recommended approach to managing your working directory.**\\\n\nA common, efficient, and trouble-free way to manage your working directory and file paths is to combine these 3 elements in an [R project](r_projects.qmd)-oriented workflow:\n\n1) An R Project to store all your files (see page on [R projects](r_projects.qmd))\\\n2) The **here** package to locate files (see page on [Import and export](importing.qmd))\\\n3) The **rio** package to import/export files (see page on [Import and export](importing.qmd))\n\n\n\n### Set by command {.unnumbered}\n\nUntil recently, many people learning R were taught to begin their scripts with a `setwd()` command. \n\nPlease instead consider using an [R project](r_projects.qmd)-oriented workflow and read the [reasons for not using `setwd()`](https://www.tidyverse.org/blog/2017/12/workflow-vs-script/). \n\nIn brief, your work becomes specific to your computer. This means that file paths used to import and export files need to be changed if used on a different computer, or by different collaborators.\n\nAs noted above, although we do not recommend this approach in most circumstances, you can use the command `setwd()` with the desired folder file path in quotations, for example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsetwd(\"C:/Documents/R Files/My analysis\")\n```\n:::\n\n\n[***DANGER:*** Setting a working directory with `setwd()` *can* be \"brittle\" if the file path is specific to one computer. Instead, use file paths relative to an R Project root directory, such as with the [**here** package]. ]{style=\"color: red;\"}\n\n\n\n### Set manually {.unnumbered}\n\nTo set the working directory manually (the point-and-click equivalent of `setwd()`), click the Session drop-down menu and go to \"Set Working Directory\" and then \"Choose Directory\". This will set the working directory for that specific R session. Note: if using this approach, you will have to do this manually each time you open RStudio.\n\n\n\n### Within an R project {.unnumbered}\n\nIf using an R project, the working directory will default to the R project root folder that contains the \".rproj\" file. This will apply if you open RStudio by clicking open the R Project (the file with \".rproj\" extension).\n\n\n\n### Working directory in an R markdown {.unnumbered}\n\nIn an R markdown script, the default working directory is the folder the Rmarkdown file (`.Rmd`) is saved within. If using an R project and **here** package, this does not apply and the working directory will be `here()` as explained in the [R projects](r_projects.qmd) page.\n\nIf you want to change the working directory of a stand-alone R markdown (not in an R project), if you use `setwd()` this will only apply to that specific code chunk. To make the change for all code chunks in an R markdown, edit the setup chunk to add the `root.dir =` parameter, such as below:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nknitr::opts_knit$set(root.dir = 'desired/directorypath')\n```\n:::\n\n\nIt is much easier to just use the R markdown within an R project and use the **here** package.\n\n\n\n### Providing file paths {.unnumbered}\n\nPerhaps the most common source of frustration for an R beginner (at least on a Windows machine) is typing in a file path to import or export data. There is a thorough explanation of how to best input file paths in the [Import and export](importing.qmd) page, but here are a few key points:\n\n**Broken paths**\n\nBelow is an example of an \"absolute\" or \"full address\" file path. These will likely break if used by another computer. One exception is if you are using a shared/network drive.\n\n C:/Users/Name/Document/Analytic Software/R/Projects/Analysis2019/data/March2019.csv \n\n**Slash direction**\n\n*If typing in a file path, be aware the direction of the slashes.* \n\nUse *forward slashes* (`/`) to separate the components (\"data/provincial.csv\"). For Windows users, the default way that file paths are displayed is with *back slashes* (\\\\) - so you will need to change the direction of each slash. If you use the **here** package as described in the [R projects](r_projects.qmd) page the slash direction is not an issue.\n\n**Relative file paths**\n\nWe generally recommend providing \"relative\" filepaths instead - that is, the path *relative to* the root of your R Project. You can do this using the **here** package as explained in the [R projects](r_projects.qmd) page. A relativel filepath might look like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Import csv linelist from the data/linelist/clean/ sub-folders of an R project\nlinelist <- import(here(\"data\", \"clean\", \"linelists\", \"marin_country.csv\"))\n```\n:::\n\n\nEven if using relative file paths within an R project, you can still use absolute paths to import and export data outside your R project.\n\n\n\n## Objects {#objects}\n\nEverything in R is an object, and R is an \"object-oriented\" language. These sections will explain:\n\n- How to create objects (`<-`).\n- Types of objects (e.g. data frames, vectors..).\\\n- How to access subparts of objects (e.g. variables in a dataset).\\\n- Classes of objects (e.g. numeric, logical, integer, double, character, factor).\n\n\n\n### Everything is an object {.unnumbered}\n\n*This section is adapted from the [R4Epis project](https://r4epis.netlify.app/training/r_basics/objects/).*\\\n\nEverything you store in R - datasets, variables, a list of village names, a total population number, even outputs such as graphs - are **objects** which are **assigned a name** and **can be referenced** in later commands.\n\nAn object exists when you have assigned it a value (see the assignment section below). When it is assigned a value, the object appears in the Environment (see the upper right pane of RStudio). It can then be operated upon, manipulated, changed, and re-defined.\n\n\n\n### Defining objects (`<-`) {.unnumbered}\n\n**Create objects *by assigning them a value* with the \\<- operator.**\\\nYou can think of the assignment operator `<-` as the words \"is defined as\". Assignment commands generally follow a standard order:\n\n**object_name** \\<- **value** (or process/calculation that produce a value)\n\nFor example, you may want to record the current epidemiological reporting week as an object for reference in later code. In this example, the object `current_week` is created when it is assigned the value `\"2018-W10\"` (the quote marks make this a character value). The object `current_week` will then appear in the RStudio Environment pane (upper-right) and can be referenced in later commands.\n\nSee the R commands and their output in the boxes below.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurrent_week <- \"2018-W10\" # this command creates the object current_week by assigning it a value\ncurrent_week # this command prints the current value of current_week object in the console\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"2018-W10\"\n```\n\n\n:::\n:::\n\n\n[***NOTE:*** Note the `[1]` in the R console output is simply indicating that you are viewing the first item of the output]{style=\"color: black;\"}\n\n[***CAUTION:*** **An object's value can be over-written** at any time by running an assignment command to re-define its value. Thus, the **order of the commands run is very important**.]{style=\"color: orange;\"}\n\nThe following command will re-define the value of `current_week`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurrent_week <- \"2018-W51\" # assigns a NEW value to the object current_week\ncurrent_week # prints the current value of current_week in the console\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"2018-W51\"\n```\n\n\n:::\n:::\n\n\n**Equals signs `=`**\n\nYou will also see equals signs in R code:\n\n- A double equals sign `==` between two objects or values asks a logical *question*: \"is this equal to that?\".\\\n- You will also see equals signs within functions used to specify values of function arguments (read about these in sections below), for example `max(age, na.rm = TRUE)`.\\\n- You *can* use a single equals sign `=` in place of `<-` to create and define objects, but this is discouraged. You can read about why this is discouraged [here](https://renkun.me/2014/01/28/difference-between-assignment-operators-in-r/).\n\n**Datasets**\n\nDatasets are also objects (typically \"data frames\") and must be assigned names when they are imported. In the code below, the object `linelist` is created and assigned the value of a CSV file imported with the **rio** package and its `import()` function.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# linelist is created and assigned the value of the imported CSV file\nlinelist <- import(\"my_linelist.csv\")\n```\n:::\n\n\nYou can read more about importing and exporting datasets with the section on [Import and export](importing.qmd).\n\n[***CAUTION:*** A quick note on naming of objects:]{style=\"color: orange;\"}\n\n- Object names must not contain spaces, but you should use underscore (\\_) or a period (.) instead of a space.\\\n- Object names are case-sensitive (meaning that Dataset_A is different from dataset_A).\n- Object names must begin with a letter (they cannot begin with a number like 1, 2 or 3).\n\n**Outputs**\n\nOutputs like tables and plots provide an example of how outputs can be saved as objects, or just be printed without being saved. A cross-tabulation of gender and outcome using the **base** R function `table()` can be printed directly to the R console (*without* being saved).\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# printed to R console only\ntable(linelist$gender, linelist$outcome)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n \n Death Recover\n f 1227 953\n m 1228 950\n```\n\n\n:::\n:::\n\n\nBut the same table can be saved as a named object. Then, optionally, it can be printed.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# save\ngen_out_table <- table(linelist$gender, linelist$outcome)\n\n# print\ngen_out_table\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n \n Death Recover\n f 1227 953\n m 1228 950\n```\n\n\n:::\n:::\n\n\n**Columns**\n\nColumns in a dataset are also objects and can be defined, over-written, and created as described below in the section on [Columns](https://epirhandbook.com/new_pages/basics.html#columnsvariables).\n\nYou can use the assignment operator from **base** R to create a new column. Below, the new column `bmi` (Body Mass Index) is created, and for each row the new value is result of a mathematical operation on the row's value in the `wt_kg` and `ht_cm` columns.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# create new \"bmi\" column using base R syntax\nlinelist$bmi <- linelist$wt_kg / (linelist$ht_cm/100)^2\n```\n:::\n\n\nHowever, in this handbook, we emphasize a different approach to defining columns, which uses the function `mutate()` from the **dplyr** package and *piping* with the pipe operator (`%>%`). The syntax is easier to read and there are other advantages explained in the page on [Cleaning data and core functions](cleaning.qmd). You can read more about *piping* in the Piping section below.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# create new \"bmi\" column using dplyr syntax\nlinelist <- linelist %>% \n mutate(bmi = wt_kg / (ht_cm/100)^2)\n```\n:::\n\n\n\n\n### Object structure {.unnumbered}\n\n**Objects can be a single piece of data (e.g. `my_number <- 24`), or they can consist of structured data.**\n\nThe graphic below is borrowed from [this online R tutorial](http://venus.ifca.unican.es/Rintro/dataStruct.html). It shows some common data structures and their names. Not included in this image is spatial data, which is discussed in the [GIS basics](gis.qmd) page.\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/R_data_structures.png){fig-align='center' width=75% height=50%}\n:::\n:::\n\n\nIn epidemiology (and particularly field epidemiology), you will *most commonly* encounter data frames and vectors:\n\n+------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+\n| Common structure | Explanation | Example |\n+------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+\n\n| Vectors | A container for a sequence of singular objects, all of the same class (e.g. numeric, character). | **\"Variables\" (columns) in data frames are vectors** (e.g. the column `age_years`). |\n+------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+\n| Data Frames | Vectors (e.g. columns) that are bound together that all have the same number of rows. | `linelist` is a data frame. |\n+------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------+\n\nNote that to create a vector that \"stands alone\" (is not part of a data frame) the function `c()` is used to combine the different elements. For example, if creating a vector of colors plot's color scale: `vector_of_colors <- c(\"blue\", \"red2\", \"orange\", \"grey\")`\n\n\n\n### Object classes {.unnumbered}\n\nAll the objects stored in R have a *class* which tells R how to handle the object. There are many possible classes, but common ones include:\n\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| Class | Explanation | Examples |\n+============+=========================================================================================================================================================================================+=======================================================================================================+\n| Character | These are text/words/sentences **\"within quotation marks\"**. Math cannot be done on these objects. | \"Character objects are in quotation marks\" |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| Integer | Numbers that are **whole only** (no decimals) | -5, 14, or 2000 |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| Numeric | These are numbers and **can include decimals**. If within quotation marks they will be considered character class. | 23.1 or 14 |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| Factor | These are vectors that have a **specified order** or hierarchy of values | An variable of economic status with ordered values |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| Date | **Once R is told that certain data are Dates**, these data can be manipulated and displayed in special ways. See the page on [Working with dates](dates.qmd) for more information. | 2018-04-12 or 15/3/1954 or Wed 4 Jan 1980 \n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| Logical | Values must be one of the two special values TRUE or FALSE (note these are **not** \"TRUE\" and \"FALSE\" in quotation marks) | TRUE or FALSE |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| data.frame | A data frame is how R stores a **typical dataset**. It consists of vectors (columns) of data bound together, that all have the same number of observations (rows). | The example AJS dataset named `linelist_raw` contains 68 variables with 300 observations (rows) each. |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| tibble | tibbles are a variation on data frame, the main operational difference being that they print more nicely to the console (display first 10 rows and only columns that fit on the screen) | Any data frame, list, or matrix can be converted to a tibble with `as_tibble()` |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n| list | A list is like vector, but holds other objects that can be other different classes | A list could hold a single number, and a data frame, and a vector, and even another list within it! |\n+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------+\n\n**You can test the class of an object by providing its name to the function `class()`**. Note: you can reference a specific column within a dataset using the `$` notation to separate the name of the dataset and the name of the column.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nclass(linelist) # class should be a data frame or tibble\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"data.frame\"\n```\n\n\n:::\n\n```{.r .cell-code}\nclass(linelist$age) # class should be numeric\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"numeric\"\n```\n\n\n:::\n\n```{.r .cell-code}\nclass(linelist$gender) # class should be character\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"character\"\n```\n\n\n:::\n:::\n\n\nSometimes, a column will be converted to a different class automatically by R. Watch out for this! For example, if you have a vector or column of numbers, but a character value is inserted... the entire column will change to class character.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnum_vector <- c(1, 2 , 3, 4, 5) # define vector as all numbers\nclass(num_vector) # vector is numeric class\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"numeric\"\n```\n\n\n:::\n\n```{.r .cell-code}\nnum_vector[3] <- \"three\" # convert the third element to a character\nclass(num_vector) # vector is now character class\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"character\"\n```\n\n\n:::\n:::\n\n\nOne common example of this is when manipulating a data frame in order to print a table - if you make a total row and try to paste/glue together percents in the same cell as numbers (e.g. `23 (40%)`), the entire numeric column above will convert to character and can no longer be used for mathematical calculations.**Sometimes, you will need to convert objects or columns to another class.**\n\n+------------------+---------------------------------------------------------------------------------------+\n| Function | Action |\n+==================+=======================================================================================+\n| `as.character()` | Converts to character class |\n+------------------+---------------------------------------------------------------------------------------+\n| `as.numeric()` | Converts to numeric class |\n+------------------+---------------------------------------------------------------------------------------+\n| `as.integer()` | Converts to integer class |\n+------------------+---------------------------------------------------------------------------------------+\n| `as.Date()` | Converts to Date class - Note: see section on [dates](dates.qmd) for details |\n+------------------+---------------------------------------------------------------------------------------+\n| `factor()` | Converts to factor - Note: re-defining order of value levels requires extra arguments |\n+------------------+---------------------------------------------------------------------------------------+\n\nLikewise, there are **base** R functions to check whether an object IS of a specific class, such as `is.numeric()`, `is.character()`, `is.double()`, `is.factor()`, `is.integer()`\n\nHere is [more online material on classes and data structures in R](https://swcarpentry.github.io/r-novice-inflammation/13-supp-data-structures.html).\n\n\n\n### Columns/Variables (`$`) {.unnumbered}\n\n**A column in a data frame is technically a \"vector\" (see table above)** - a series of values that must all be the same class (either character, numeric, logical, etc).\n\nA vector can exist independent of a data frame, for example a vector of column names that you want to include as explanatory variables in a model. To create a \"stand alone\" vector, use the `c()` function as below:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# define the stand-alone vector of character values\nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n\n# print the values in this named vector\nexplanatory_vars\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"gender\" \"fever\" \"chills\" \"cough\" \"aches\" \"vomit\" \n```\n\n\n:::\n:::\n\n\n**Columns in a data frame are also vectors and can be called, referenced, extracted, or created using the `$` symbol.** The `$` symbol connects the name of the column to the name of its data frame. In this handbook, we try to use the word \"column\" instead of \"variable\".\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Retrieve the length of the vector age_years\nlength(linelist$age) # (age is a column in the linelist data frame)\n```\n:::\n\n\nBy typing the name of the data frame followed by `$` you will also see a drop-down menu of all columns in the data frame. You can scroll through them using your arrow key, select one with your Enter key, and avoid spelling mistakes!\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/Calling_Names.gif){fig-align='center' width=100%}\n:::\n:::\n\n\n[***ADVANCED TIP:*** Some more complex objects (e.g. a list, or an `epicontacts` object) may have multiple levels which can be accessed through multiple dollar signs. For example `epicontacts$linelist$date_onset`]{style=\"color: darkgreen;\"}\n\n\n\n### Access/index with brackets (`[ ]`) {.unnumbered}\n\nYou may need to view parts of objects, also called \"indexing\", which is often done using the square brackets `[ ]`. Using `$` on a data frame to access a column is also a type of indexing.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_vector <- c(\"a\", \"b\", \"c\", \"d\", \"e\", \"f\") # define the vector\nmy_vector[5] # print the 5th element\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"e\"\n```\n\n\n:::\n:::\n\n\nSquare brackets also work to return specific parts of an returned output, such as the output of a `summary()` function:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# All of the summary\nsummary(linelist$age)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.07 23.00 84.00 86 \n```\n\n\n:::\n\n```{.r .cell-code}\n# Just the second element of the summary, with name (using only single brackets)\nsummary(linelist$age)[2]\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n1st Qu. \n 6 \n```\n\n\n:::\n\n```{.r .cell-code}\n# Just the second element, without name (using double brackets)\nsummary(linelist$age)[[2]]\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 6\n```\n\n\n:::\n\n```{.r .cell-code}\n# Extract an element by name, without showing the name\nsummary(linelist$age)[[\"Median\"]]\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 13\n```\n\n\n:::\n:::\n\n\nBrackets also work on data frames to view specific rows and columns. You can do this using the syntax `data frame[rows, columns]`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# View a specific row (2) from dataset, with all columns (don't forget the comma!)\nlinelist[2,]\n\n# View all rows, but just one column\nlinelist[, \"date_onset\"]\n\n# View values from row 2 and columns 5 through 10\nlinelist[2, 5:10] \n\n# View values from row 2 and columns 5 through 10 and 18\nlinelist[2, c(5:10, 18)] \n\n# View rows 2 through 20, and specific columns\nlinelist[2:20, c(\"date_onset\", \"outcome\", \"age\")]\n\n# View rows and columns based on criteria\n# *** Note the data frame must still be named in the criteria!\nlinelist[linelist$age > 25 , c(\"date_onset\", \"outcome\", \"age\")]\n\n# Use View() to see the outputs in the RStudio Viewer pane (easier to read) \n# *** Note the capital \"V\" in View() function\nView(linelist[2:20, \"date_onset\"])\n\n# Save as a new object\nnew_table <- linelist[2:20, c(\"date_onset\")] \n```\n:::\n\n\nNote that you can also achieve the above row/column indexing on data frames and tibbles using **dplyr** syntax (functions `filter()` for rows, and `select()` for columns). Read more about these core functions in the [Cleaning data and core functions](cleaning.qmd) page.\n\nTo filter based on \"row number\", you can use the **dplyr** function `row_number()` with open parentheses as part of a logical filtering statement. Often you will use the `%in%` operator and a range of numbers as part of that logical statement, as shown below. To see the *first* N rows, you can also use the special **dplyr** function `head()`. Note, there is a function `head()` from **base** R, but this is overwritten by the **dplyr** function when you load **tidyverse**.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# View first 100 rows\nlinelist %>% head(100)\n\n# Show row 5 only\nlinelist %>% filter(row_number() == 5)\n\n# View rows 2 through 20, and three specific columns (note no quotes necessary on column names)\nlinelist %>% \n filter(row_number() %in% 2:20) %>% \n select(date_onset, outcome, age)\n```\n:::\n\n\nWhen indexing an object of class **list**, single brackets always return with class list, even if only a single object is returned. Double brackets, however, can be used to access a single element and return a different class than list. Brackets can also be written after one another, as demonstrated below.\n\nThis [visual explanation of lists indexing, with pepper shakers](https://r4ds.had.co.nz/vectors.html#lists-of-condiments) is humorous and helpful.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# define demo list\nmy_list <- list(\n # First element in the list is a character vector\n hospitals = c(\"Central\", \"Empire\", \"Santa Anna\"),\n \n # second element in the list is a data frame of addresses\n addresses = data.frame(\n street = c(\"145 Medical Way\", \"1048 Brown Ave\", \"999 El Camino\"),\n city = c(\"Andover\", \"Hamilton\", \"El Paso\")\n )\n )\n```\n:::\n\n\nHere is how the list looks when printed to the console. See how there are two named elements:\n\n- `hospitals`, a character vector\\\n- `addresses`, a data frame of addresses\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_list\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n$hospitals\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n\n$addresses\n street city\n1 145 Medical Way Andover\n2 1048 Brown Ave Hamilton\n3 999 El Camino El Paso\n```\n\n\n:::\n:::\n\n\nNow we extract, using various methods:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_list[1] # this returns the element in class \"list\" - the element name is still displayed\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n$hospitals\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n```\n\n\n:::\n\n```{.r .cell-code}\nmy_list[[1]] # this returns only the (unnamed) character vector\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n```\n\n\n:::\n\n```{.r .cell-code}\nmy_list[[\"hospitals\"]] # you can also index by name of the list element\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Central\" \"Empire\" \"Santa Anna\"\n```\n\n\n:::\n\n```{.r .cell-code}\nmy_list[[1]][3] # this returns the third element of the \"hospitals\" character vector\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Santa Anna\"\n```\n\n\n:::\n\n```{.r .cell-code}\nmy_list[[2]][1] # This returns the first column (\"street\") of the address data frame\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n street\n1 145 Medical Way\n2 1048 Brown Ave\n3 999 El Camino\n```\n\n\n:::\n:::\n\n\n\n\n### Remove objects {.unnumbered}\n\nYou can remove individual objects from your R environment by putting the name in the `rm()` function (no quote marks):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrm(object_name)\n```\n:::\n\n\nYou can remove all objects (clear your workspace) by running:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrm(list = ls(all = TRUE))\n```\n:::\n\n\n\n\n\n\n\n\n## Piping (`%>%`)\n\n**Two general approaches to working with objects are:**\n\n1) **Pipes/tidyverse** - pipes send an object from function to function - emphasis is on the *action*, not the object.\\\n2) **Define intermediate objects** - an object is re-defined again and again - emphasis is on the object.\n\n\n\n### **Pipes** {.unnumbered}\n\n**Simply explained, the pipe operator passes an intermediate output from one function to the next.**\\\nYou can think of it as saying \"and then\". Many functions can be linked together with `%>%`.\n\n- **Piping emphasizes a sequence of actions, not the object the actions are being performed on**.\\\n- Pipes are best when a sequence of actions must be performed on one object.\\\n- Pipes can make code more clean and easier to read, more intuitive.\n\nPipe operators were first introduced through the [magrittr package](https://magrittr.tidyverse.org/), which is part of tidyverse, and were specified as `%>%`. In R 4.1.0, they introduced a **base** R pipe which is specified through `|>`. The behaviour of the two pipes is the same, and they can be used somewhat interchangeably. However, there are a few key differences.\n\n* The `%>%` pipe allows you to pass multiple arguments.\n* The `%>%` pipe lets you drop parenthesis when calling a function with no other arguments (i.e. drop vs drop()).\n* The `%>%` pipe allows you to start a pipe with `.` to create a function in your linking of code.\n\nFor these reasons, we recommend the **magrittr** pipe, `%>%`, over the **base** R pipe, `|>`.\n\nTo read more about the differences between base R and tidyverse (magrittr) pipes, see this [blog post](https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/). For more information on the tidyverse approach, please see this [style guide](https://style.tidyverse.org/pipes.html).\n\nHere is a fake example for comparison, using fictional functions to \"bake a cake\". First, the pipe method:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# A fake example of how to bake a cake using piping syntax\n\ncake <- flour %>% # to define cake, start with flour, and then...\n add(eggs) %>% # add eggs\n add(oil) %>% # add oil\n add(water) %>% # add water\n mix_together( # mix together\n utensil = spoon,\n minutes = 2) %>% \n bake(degrees = 350, # bake\n system = \"fahrenheit\",\n minutes = 35) %>% \n let_cool() # let it cool down\n```\n:::\n\n\nNote that just like other R commands, pipes can be used to just display the result, or to save/re-save an object, depending on whether the assignment operator `<-` is involved. See both below:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Create or overwrite object, defining as aggregate counts by age category (not printed)\nlinelist_summary <- linelist %>% \n count(age_cat)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\n# Print the table of counts in the console, but don't save it\nlinelist %>% \n count(age_cat)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat n\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 86\n```\n\n\n:::\n:::\n\n\n**`%<>%`**\\\nThis is an \"assignment pipe\" from the **magrittr** package, which *pipes an object forward and also re-defines the object*. It must be the first pipe operator in the chain. It is shorthand. The below two commands are equivalent:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist <- linelist %>%\n filter(age > 50)\n\nlinelist %<>% filter(age > 50)\n```\n:::\n\n\n\n\n### Define intermediate objects {.unnumbered}\n\nThis approach to changing objects/data frames may be better if:\n\n- You need to manipulate multiple objects\\\n- There are intermediate steps that are meaningful and deserve separate object names\n\n**Risks:**\n\n- Creating new objects for each step means creating lots of objects. If you use the wrong one you might not realize it!\\\n- Naming all the objects can be confusing.\\\n- Errors may not be easily detectable.\n\nEither name each intermediate object, or overwrite the original, or combine all the functions together. All come with their own risks.\n\nBelow is the same fake \"cake\" example as above, but using this style:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# a fake example of how to bake a cake using this method (defining intermediate objects)\nbatter_1 <- left_join(flour, eggs)\nbatter_2 <- left_join(batter_1, oil)\nbatter_3 <- left_join(batter_2, water)\n\nbatter_4 <- mix_together(object = batter_3, utensil = spoon, minutes = 2)\n\ncake <- bake(batter_4, degrees = 350, system = \"fahrenheit\", minutes = 35)\n\ncake <- let_cool(cake)\n```\n:::\n\n\nCombine all functions together - this is difficult to read:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# an example of combining/nesting mutliple functions together - difficult to read\ncake <- let_cool(bake(mix_together(batter_3, utensil = spoon, minutes = 2), degrees = 350, system = \"fahrenheit\", minutes = 35))\n```\n:::\n\n\n\n\n## Key operators and functions {#operators}\n\nThis section details operators in R, such as:\n\n- Definitional operators.\\\n- Relational operators (less than, equal too..).\\\n- Logical operators (and, or...).\\\n- Handling missing values.\\\n- Mathematical operators and functions (+/-, \\>, sum(), median(), ...).\\\n- The `%in%` operator.\n\n\n\n### Assignment operators {.unnumbered}\n\n**`<-`**\n\nThe basic assignment operator in R is `<-`. Such that `object_name <- value`.\\\nThis assignment operator can also be written as `=`. We advise use of `<-` for general R use. We also advise surrounding such operators with spaces, for readability.\n\n**`<<-`**\n\nIf [Writing functions](writing_functions.qmd), or using R in an interactive way with sourced scripts, then you may need to use this assignment operator `<<-` (from **base** R). This operator is used to define an object in a higher 'parent' R Environment. See this [online reference](https://stat.ethz.ch/R-manual/R-devel/library/base/html/assignOps.html).\n\n**`%<>%`**\n\nThis is an \"assignment pipe\" from the **magrittr** package, which pipes an object forward and *also re-defines the object*. It must be the first pipe operator in the chain. It is shorthand, as shown below in two equivalent examples:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist <- linelist %>% \n mutate(age_months = age_years * 12)\n```\n:::\n\n\nThe above is equivalent to the below:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %<>% mutate(age_months = age_years * 12)\n```\n:::\n\n\n**`%<+%`**\n\nThis is used to add data to phylogenetic trees with the **ggtree** package. See the page on [Phylogenetic trees](phylogenetic_trees.qmd) or this online [resource book](https://yulab-smu.top/treedata-book/).\n\n\n\n### Relational and logical operators {.unnumbered}\n\nRelational operators compare values and are often used when defining new variables and subsets of datasets. Here are the common relational operators in R:\n\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Meaning | Operator | Example | Example Result |\n+==========================+============+==============+========================================================================================================================================================+\n| Equal to | `==` | `\"A\" == \"a\"` | `FALSE` (because R is case sensitive) *Note that == (double equals) is different from = (single equals), which acts like the assignment operator `<-`* |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Not equal to | `!=` | `2 != 0` | `TRUE` |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Greater than | `>` | `4 > 2` | `TRUE` |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Less than | `<` | `4 < 2` | `FALSE` |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Greater than or equal to | `>=` | `6 >= 4` | `TRUE` |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Less than or equal to | `<=` | `6 <= 4` | `FALSE` |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Value is missing | `is.na()` | `is.na(7)` | `FALSE` (see page on [Missing data](missing_data.qmd)) |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n| Value is not missing | `!is.na()` | `!is.na(7)` | `TRUE` |\n+--------------------------+------------+--------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n\nLogical operators, such as AND and OR, are often used to connect relational operators and create more complicated criteria. Complex statements might require parentheses `( )` for grouping and order of application.\n\n+---------------------+-----------------------------------------------------------------------+\n| Meaning | Operator |\n+=====================+=======================================================================+\n| AND | `&` |\n+---------------------+-----------------------------------------------------------------------+\n| OR | `|` (vertical bar) |\n+---------------------+-----------------------------------------------------------------------+\n| Parentheses | `( )` Used to group criteria together and clarify order of operations |\n+---------------------+-----------------------------------------------------------------------+\n\nFor example, below, we have a linelist with two variables we want to use to create our case definition, `hep_e_rdt`, a test result and `other_cases_in_hh`, which will tell us if there are other cases in the household. The command below uses the function `case_when()` to create the new variable `case_def` such that:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist_cleaned <- linelist %>%\n mutate(case_def = case_when(\n is.na(rdt_result) & is.na(other_case_in_home) ~ NA_character_,\n rdt_result == \"Positive\" ~ \"Confirmed\",\n rdt_result != \"Positive\" & other_cases_in_home == \"Yes\" ~ \"Probable\",\n TRUE ~ \"Suspected\"\n ))\n```\n:::\n\n\n+------------------------------------------------------------------------------------------------+--------------------------------------------+\n| Criteria in example above | Resulting value in new variable \"case_def\" |\n+================================================================================================+============================================+\n| If the value for variables `rdt_result` and `other_cases_in_home` are missing | `NA` (missing) |\n+------------------------------------------------------------------------------------------------+--------------------------------------------+\n| If the value in `rdt_result` is \"Positive\" | \"Confirmed\" |\n+------------------------------------------------------------------------------------------------+--------------------------------------------+\n| If the value in `rdt_result` is NOT \"Positive\" AND the value in `other_cases_in_home` is \"Yes\" | \"Probable\" |\n+------------------------------------------------------------------------------------------------+--------------------------------------------+\n| If one of the above criteria are not met | \"Suspected\" |\n+------------------------------------------------------------------------------------------------+--------------------------------------------+\n\n*Note that R is case-sensitive, so \"Positive\" is different than \"positive\".*\n\n\n\n### Missing values {.unnumbered}\n\nIn R, missing values are represented by the special value `NA` (a \"reserved\" value) (capital letters N and A - not in quotation marks). If you import data that records missing data in another way (e.g. 99, \"Missing\"), you may want to re-code those values to `NA`. How to do this is addressed in the [Import and export](importing.qmd) page.\n\n**To test whether a value is `NA`, use the special function `is.na()`**, which returns `TRUE` or `FALSE`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrdt_result <- c(\"Positive\", \"Suspected\", \"Positive\", NA) # two positive cases, one suspected, and one unknown\nis.na(rdt_result) # Tests whether the value of rdt_result is NA\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] FALSE FALSE FALSE TRUE\n```\n\n\n:::\n:::\n\n\nRead more about missing, infinite, `NULL`, and impossible values in the page on [Missing data](missing_data.qmd). Learn how to convert missing values when importing data in the page on [Import and export](importing.qmd).\n\n\n\n### Mathematics and statistics {.unnumbered}\n\nAll the operators and functions in this page are automatically available using **base** R.\n\n#### Mathematical operators {.unnumbered}\n\nThese are often used to perform addition, division, to create new columns, etc. Below are common mathematical operators in R. Whether you put spaces around the operators is not important.\n\n| Purpose | Example in R |\n|---------------------|--------------|\n| addition | 2 + 3 |\n| subtraction | 2 - 3 |\n| multiplication | 2 \\* 3 |\n| division | 30 / 5 |\n| exponent | 2\\^3 |\n| order of operations | ( ) |\n\n#### Mathematical functions {.unnumbered}\n\n| Purpose | Function |\n|--------------------|---------------------------------------|\n| rounding | round(x, digits = n) |\n| rounding | janitor::round_half_up(x, digits = n) |\n| ceiling (round up) | ceiling(x) |\n| floor (round down) | floor(x) |\n| absolute value | abs(x) |\n| square root | sqrt(x) |\n| exponent | exponent(x) |\n| natural logarithm | log(x) |\n| log base 10 | log10(x) |\n| log base 2 | log2(x) |\n\nNote: for `round()` the `digits =` specifies the number of decimal placed. Use `signif()` to round to a number of significant figures.\n\n#### Scientific notation {.unnumbered}\n\nThe likelihood of scientific notation being used depends on the value of the `scipen` option.\n\nFrom the documentation of `?options`: scipen is a penalty to be applied when deciding to print numeric values in fixed or exponential notation. Positive values bias towards fixed and negative towards scientific notation: fixed notation will be preferred unless it is more than 'scipen' digits wider.\n\nIf it is set to a low number (e.g. 0) it will be \"turned on\" always. To \"turn off\" scientific notation in your R session, set it to a very high number, for example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# turn off scientific notation\noptions(scipen = 999)\n```\n:::\n\n\n#### Rounding {.unnumbered}\n\n[***DANGER:*** `round()` uses \"banker's rounding\" which rounds up from a .5 only if the upper number is even. Use `round_half_up()` from **janitor** to consistently round halves up to the nearest whole number. See [this explanation](https://cran.r-project.org/web/packages/janitor/vignettes/janitor.html#explore-records-with-duplicated-values-for-specific-combinations-of-variables-with-get_dupes) ]{style=\"color: red;\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# use the appropriate rounding function for your work\nround(c(2.5, 3.5))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 2 4\n```\n\n\n:::\n\n```{.r .cell-code}\njanitor::round_half_up(c(2.5, 3.5))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 3 4\n```\n\n\n:::\n:::\n\n\n#### Statistical functions {.unnumbered}\n\n[***CAUTION:*** The functions below will by default include missing values in calculations. Missing values will result in an output of `NA`, unless the argument `na.rm = TRUE` is specified. This can be written shorthand as `na.rm = T`.]{style=\"color: orange;\"}\n\n| Objective | Function |\n|-------------------------|----------------------|\n| mean (average) | mean(x, na.rm = T) |\n| median | median(x, na.rm= T) |\n| standard deviation | sd(x, na.rm = T) |\n| quantiles\\* | quantile(x, probs) |\n| sum | sum(x, na.rm = T) |\n| minimum value | min(x, na.rm = T) |\n| maximum value | max(x, na.rm = T) |\n| range of numeric values | range(x, na.rm = T) |\n| summary\\*\\* | summary(x) |\n\nNotes:\n\n- `*quantile()`: `x` is the numeric vector to examine, and `probs =` is a numeric vector with probabilities within 0 and 1.0, e.g `c(0.5, 0.8, 0.85)`.\n- `**summary()`: gives a summary on a numeric vector including mean, median, and common percentiles.\n\n[***DANGER:*** If providing a vector of numbers to one of the above functions, be sure to wrap the numbers within `c()` .]{style=\"color: red;\"}\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# If supplying raw numbers to a function, wrap them in c()\nmean(1, 6, 12, 10, 5, 0) # !!! INCORRECT !!! \n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 1\n```\n\n\n:::\n\n```{.r .cell-code}\nmean(c(1, 6, 12, 10, 5, 0)) # CORRECT\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.666667\n```\n\n\n:::\n:::\n\n\n#### Other useful functions {.unnumbered}\n\n+----------------------------+-------------------+-------------------------------------------------+\n| Objective | Function | Example |\n+============================+===================+=================================================+\n| create a sequence | seq(from, to, by) | `seq(1, 10, 2)` |\n+----------------------------+-------------------+-------------------------------------------------+\n| repeat x, n times | rep(x, ntimes) | `rep(1:3, 2)` or `rep(c(\"a\", \"b\", \"c\"), 3)` |\n+----------------------------+-------------------+-------------------------------------------------+\n| subdivide a numeric vector | cut(x, n) | `cut(linelist$age, 5)` |\n+----------------------------+-------------------+-------------------------------------------------+\n| take a random sample | sample(x, size) | `sample(linelist$id, size = 5, replace = TRUE)` |\n+----------------------------+-------------------+-------------------------------------------------+\n\n\n\n### `%in%` {.unnumbered}\n\nA very useful operator for matching values, and for quickly assessing if a value is within a vector or data frame.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_vector <- c(\"a\", \"b\", \"c\", \"d\")\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\n\"a\" %in% my_vector\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] TRUE\n```\n\n\n:::\n\n```{.r .cell-code}\n\"h\" %in% my_vector\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] FALSE\n```\n\n\n:::\n:::\n\n\nTo ask if a value is **not** `%in%` a vector, put an exclamation mark (!) **in front** of the logic statement:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# to negate, put an exclamation in front\n!\"a\" %in% my_vector\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] FALSE\n```\n\n\n:::\n\n```{.r .cell-code}\n!\"h\" %in% my_vector\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] TRUE\n```\n\n\n:::\n:::\n\n\n`%in%` is very useful when using the **dplyr** function `case_when()`. You can define a vector previously, and then reference it later. For example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\naffirmative <- c(\"1\", \"Yes\", \"YES\", \"yes\", \"y\", \"Y\", \"oui\", \"Oui\", \"Si\")\n\nlinelist <- linelist %>% \n mutate(child_hospitaled = case_when(\n hospitalized %in% affirmative & age < 18 ~ \"Hospitalized Child\",\n TRUE ~ \"Not\"))\n```\n:::\n\n\nNote: If you want to detect a partial string, perhaps using `str_detect()` from **stringr**, it will not accept a character vector like `c(\"1\", \"Yes\", \"yes\", \"y\")`. Instead, it must be given a *regular expression* - one condensed string with OR bars, such as \"1\\|Yes\\|yes\\|y\". For example, `str_detect(hospitalized, \"1|Yes|yes|y\")`. See the page on [Characters and strings](characters_strings.qmd) for more information.\n\nYou can convert a character vector to a named regular expression with this command:\n\n\n::: {.cell}\n\n```{.r .cell-code}\naffirmative <- c(\"1\", \"Yes\", \"YES\", \"yes\", \"y\", \"Y\", \"oui\", \"Oui\", \"Si\")\naffirmative\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"1\" \"Yes\" \"YES\" \"yes\" \"y\" \"Y\" \"oui\" \"Oui\" \"Si\" \n```\n\n\n:::\n\n```{.r .cell-code}\n# condense to \naffirmative_str_search <- paste0(affirmative, collapse = \"|\") # option with base R\naffirmative_str_search <- str_c(affirmative, collapse = \"|\") # option with stringr package\n\naffirmative_str_search\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"1|Yes|YES|yes|y|Y|oui|Oui|Si\"\n```\n\n\n:::\n:::\n\n\n\n\n\n\n\n\n## Errors & warnings\n\nThis section explains:\n\n- The difference between errors and warnings.\\\n- General syntax tips for writing R code.\\\n- Code assists.\n\nCommon errors and warnings and troubleshooting tips can be found in the page on [Errors and help](errors.qmd).\n\n\n\n### Error versus Warning {.unnumbered}\n\nWhen a command is run, the R Console may show you warning or error messages in red text.\n\n- A **warning** means that R has completed your command, but had to take additional steps or produced unusual output that you should be aware of.\n\n- An **error** means that R was not able to complete your command.\n\nLook for clues:\n\n- The error/warning message will often include a line number for the problem.\n\n- If an object \"is unknown\" or \"not found\", perhaps you spelled it incorrectly, forgot to call a package with library(), or forgot to re-run your script after making changes.\n\nIf all else fails, copy the error message into Google along with some key terms - chances are that someone else has worked through this already!\n\n\n\n### General syntax tips {.unnumbered}\n\nA few things to remember when writing commands in R, to avoid errors and warnings:\n\n- Always close parentheses - tip: count the number of opening \"(\" and closing parentheses \")\" for each code chunk.\n- Avoid spaces in column and object names. Use underscore ( \\_ ) or periods ( . ) instead.\n- Keep track of and remember to separate a function's arguments with commas.\n- R is case-sensitive, meaning `Variable_A` is *different* from `variable_A`.\n\n\n\n### Code assists {.unnumbered}\n\nAny script (RMarkdown or otherwise) will give clues when you have made a mistake. For example, if you forgot to write a comma where it is needed, or to close a parentheses, RStudio will raise a flag on that line, on the right side of the script, to warn you.\n", + "supporting": [ + "basics_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/new_pages/basics_files/figure-html/unnamed-chunk-6-1.png b/new_pages/basics_files/figure-html/unnamed-chunk-6-1.png new file mode 100644 index 00000000..acb2cfd9 Binary files /dev/null and b/new_pages/basics_files/figure-html/unnamed-chunk-6-1.png differ diff --git a/new_pages/basics_files/figure-html/unnamed-chunk-7-1.png b/new_pages/basics_files/figure-html/unnamed-chunk-7-1.png new file mode 100644 index 00000000..acb2cfd9 Binary files /dev/null and b/new_pages/basics_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/new_pages/basics_files/figure-html/unnamed-chunk-8-1.png b/new_pages/basics_files/figure-html/unnamed-chunk-8-1.png new file mode 100644 index 00000000..e1454ca3 Binary files /dev/null and b/new_pages/basics_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/new_pages/characters_strings.html b/new_pages/characters_strings.html new file mode 100644 index 00000000..2168cc20 --- /dev/null +++ b/new_pages/characters_strings.html @@ -0,0 +1,2690 @@ + + + + + + + + + +10  Characters and strings – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    10  Characters and strings

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    This page demonstrates use of the stringr package to evaluate and handle character values (“strings”).

    +
      +
    1. Combine, order, split, arrange - str_c(), str_glue(), str_order(), str_split()
    2. +
    3. Clean and standardise +
        +
      • Adjust length - str_pad(), str_trunc(), str_wrap()
      • +
      • Change case - str_to_upper(), str_to_title(), str_to_lower(), str_to_sentence()
      • +
    4. +
    5. Evaluate and extract by position - str_length(), str_sub(), word()
    6. +
    7. Patterns +
        +
      • Detect and locate - str_detect(), str_subset(), str_match(), str_extract()
      • +
      • Modify and replace - str_sub(), str_replace_all()
      • +
    8. +
    9. Regular expressions (“regex”)
    10. +
    +

    For ease of display most examples are shown acting on a short defined character vector, however they can easily be adapted to a column within a data frame.

    +

    This stringr vignette provided much of the inspiration for this page.

    + +
    +

    10.1 Preparation

    +
    +

    Load packages

    +

    Install or load the stringr and other tidyverse packages.

    +
    +
    # install/load packages
    +pacman::p_load(
    +  stringr,    # many functions for handling strings
    +  tidyverse,  # for optional data manipulation
    +  tools       # alternative for converting to title case
    +  )      
    +
    +
    +
    +

    Import data

    +

    In this page we will occassionally reference the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import data with the import() function from the rio package (it handles many file types like .xlsx, .csv, .rds - see the Import and export page for details).

    +
    +
    # import case linelist 
    +linelist <- import("linelist_cleaned.rds")
    +
    +

    The first 50 rows of the linelist are displayed below.

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    10.2 Unite, split, and arrange

    +

    This section covers:

    +
      +
    • Using str_c(), str_glue(), and unite() to combine strings
    • +
    • Using str_order() to arrange strings
    • +
    • Using str_split() and separate() to split strings
    • +
    + +
    +

    Combine strings

    +

    To combine or concatenate multiple strings into one string, we suggest using str_c from stringr. If you have distinct character values to combine, simply provide them as unique arguments, separated by commas.

    +
    +
    str_c("String1", "String2", "String3")
    +
    +
    [1] "String1String2String3"
    +
    +
    +

    The argument sep = inserts a character value between each of the arguments you provided (e.g. inserting a comma, space, or newline "\n")

    +
    +
    str_c("String1", "String2", "String3", sep = ", ")
    +
    +
    [1] "String1, String2, String3"
    +
    +
    +

    The argument collapse = is relevant if you are inputting multiple vectors as arguments to str_c(). It is used to separate the elements of what would be an output vector, such that the output vector only has one long character element.

    +

    The example below shows the combination of two vectors into one (first names and last names). Another similar example might be jurisdictions and their case counts. In this example:

    +
      +
    • The sep = value appears between each first and last name
      +
    • +
    • The collapse = value appears between each person
    • +
    +
    +
    first_names <- c("abdul", "fahruk", "janice") 
    +last_names  <- c("hussein", "akinleye", "okeke")
    +
    +# sep displays between the respective input strings, while collapse displays between the elements produced
    +str_c(first_names, last_names, sep = " ", collapse = ";  ")
    +
    +
    [1] "abdul hussein;  fahruk akinleye;  janice okeke"
    +
    +
    +

    Note: Depending on your desired display context, when printing such a combined string with newlines, you may need to wrap the whole phrase in cat() for the newlines to print properly:

    +
    +
    # For newlines to print correctly, the phrase may need to be wrapped in cat()
    +cat(str_c(first_names, last_names, sep = " ", collapse = ";\n"))
    +
    +
    abdul hussein;
    +fahruk akinleye;
    +janice okeke
    +
    +
    + +
    +
    +

    Dynamic strings

    +

    Use str_glue() to insert dynamic R code into a string. This is a very useful function for creating dynamic plot captions, as demonstrated below.

    +
      +
    • All content goes between double quotation marks str_glue("").
      +
    • +
    • Any dynamic code or references to pre-defined values are placed within curly brackets {} within the double quotation marks. There can be many curly brackets in the same str_glue() command.
      +
    • +
    • To display character quotes ’’, use single quotes within the surrounding double quotes (e.g. when providing date format - see example below).
      +
    • +
    • Tip: You can use \n to force a new line.
      +
    • +
    • Tip: You use format() to adjust date display, and use Sys.Date() to display the current date.
    • +
    +

    A simple example, of a dynamic plot caption:

    +
    +
    str_glue("Data include {nrow(linelist)} cases and are current to {format(Sys.Date(), '%d %b %Y')}.")
    +
    +
    Data include 5888 cases and are current to 18 Oct 2024.
    +
    +
    +

    An alternative format is to use placeholders within the brackets and define the code in separate arguments at the end of the str_glue() function, as below. This can improve code readability if the text is long.

    +
    +
    str_glue("Linelist as of {current_date}.\nLast case hospitalized on {last_hospital}.\n{n_missing_onset} cases are missing date of onset and not shown",
    +         current_date = format(Sys.Date(), '%d %b %Y'),
    +         last_hospital = format(as.Date(max(linelist$date_hospitalisation, na.rm=T)), '%d %b %Y'),
    +         n_missing_onset = nrow(linelist %>% filter(is.na(date_onset)))
    +         )
    +
    +
    Linelist as of 18 Oct 2024.
    +Last case hospitalized on 30 Apr 2015.
    +256 cases are missing date of onset and not shown
    +
    +
    +

    Pulling from a data frame

    +

    Sometimes, it is useful to pull data from a data frame and have it pasted together in sequence. Below is an example data frame. We will use it to to make a summary statement about the jurisdictions and the new and total case counts.

    +
    +
    # make case data frame
    +case_table <- data.frame(
    +  zone        = c("Zone 1", "Zone 2", "Zone 3", "Zone 4", "Zone 5"),
    +  new_cases   = c(3, 0, 7, 0, 15),
    +  total_cases = c(40, 4, 25, 10, 103)
    +  )
    +
    +
    +
    +
    + +
    +
    +

    Use str_glue_data(), which is specially made for taking data from data frame rows:

    +
    +
    case_table %>% 
    +  str_glue_data("{zone}: {new_cases} ({total_cases} total cases)")
    +
    +
    Zone 1: 3 (40 total cases)
    +Zone 2: 0 (4 total cases)
    +Zone 3: 7 (25 total cases)
    +Zone 4: 0 (10 total cases)
    +Zone 5: 15 (103 total cases)
    +
    +
    +

    Combine strings across rows

    +

    If you are trying to “roll-up” values in a data frame column, e.g. combine values from multiple rows into just one row by pasting them together with a separator, see the section of the De-duplication page on “rolling-up” values.

    +

    Data frame to one line

    +

    You can make the statement appear in one line using str_c() (specifying the data frame and column names), and providing sep = and collapse = arguments.

    +
    +
    str_c(case_table$zone, case_table$new_cases, sep = " = ", collapse = ";  ")
    +
    +
    [1] "Zone 1 = 3;  Zone 2 = 0;  Zone 3 = 7;  Zone 4 = 0;  Zone 5 = 15"
    +
    +
    +

    You could add the pre-fix text “New Cases:” to the beginning of the statement by wrapping with a separate str_c() (if “New Cases:” was within the original str_c() it would appear multiple times).

    +
    +
    str_c("New Cases: ", str_c(case_table$zone, case_table$new_cases, sep = " = ", collapse = ";  "))
    +
    +
    [1] "New Cases: Zone 1 = 3;  Zone 2 = 0;  Zone 3 = 7;  Zone 4 = 0;  Zone 5 = 15"
    +
    +
    +
    +
    +

    Unite columns

    +

    Within a data frame, bringing together character values from multiple columns can be achieved with unite() from tidyr. This is the opposite of separate().

    +

    Provide the name of the new united column. Then provide the names of the columns you wish to unite.

    +
      +
    • By default, the separator used in the united column is underscore _, but this can be changed with the sep = argument.
      +
    • +
    • remove = removes the input columns from the data frame (TRUE by default).
      +
    • +
    • na.rm = removes missing values while uniting (FALSE by default).
    • +
    +

    Below, we define a mini-data frame to demonstrate with:

    +
    +
    df <- data.frame(
    +  case_ID = c(1:6),
    +  symptoms  = c("jaundice, fever, chills",     # patient 1
    +                "chills, aches, pains",        # patient 2 
    +                "fever",                       # patient 3
    +                "vomiting, diarrhoea",         # patient 4
    +                "bleeding from gums, fever",   # patient 5
    +                "rapid pulse, headache"),      # patient 6
    +  outcome = c("Recover", "Death", "Death", "Recover", "Recover", "Recover"))
    +
    +
    +
    df_split <- separate(df, symptoms, into = c("sym_1", "sym_2", "sym_3"), extra = "merge")
    +
    +
    Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2 rows [3, 4].
    +
    +
    +

    Here is the example data frame:

    +
    +
    +
    + +
    +
    +

    Below, we unite the three symptom columns:

    +
    +
    df_split %>% 
    +  unite(
    +    col = "all_symptoms",         # name of the new united column
    +    c("sym_1", "sym_2", "sym_3"), # columns to unite
    +    sep = ", ",                   # separator to use in united column
    +    remove = TRUE,                # if TRUE, removes input cols from the data frame
    +    na.rm = TRUE                  # if TRUE, missing values are removed before uniting
    +  )
    +
    +
      case_ID                all_symptoms outcome
    +1       1     jaundice, fever, chills Recover
    +2       2        chills, aches, pains   Death
    +3       3                       fever   Death
    +4       4         vomiting, diarrhoea Recover
    +5       5 bleeding, from, gums, fever Recover
    +6       6      rapid, pulse, headache Recover
    +
    +
    + +
    +
    +

    Split

    +

    To split a string based on a pattern, use str_split(). It evaluates the string(s) and returns a list of character vectors consisting of the newly-split values.

    +

    The simple example below evaluates one string and splits it into three. By default it returns an object of class list with one element (a character vector) for each string initially provided. If simplify = TRUE it returns a character matrix.

    +

    In this example, one string is provided, and the function returns a list with one element - a character vector with three values.

    +
    +
    str_split(string = "jaundice, fever, chills",
    +          pattern = ",")
    +
    +
    [[1]]
    +[1] "jaundice" " fever"   " chills" 
    +
    +
    +

    If the output is saved, you can then access the nth split value with bracket syntax. To access a specific value you can use syntax like this: the_returned_object[[1]][2], which would access the second value from the first evaluated string (“fever”). See the R basics page for more detail on accessing elements.

    +
    +
    pt1_symptoms <- str_split("jaundice, fever, chills", ",")
    +
    +pt1_symptoms[[1]][2]  # extracts 2nd value from 1st (and only) element of the list
    +
    +
    [1] " fever"
    +
    +
    +

    If multiple strings are provided by str_split(), there will be more than one element in the returned list.

    +
    +
    symptoms <- c("jaundice, fever, chills",     # patient 1
    +              "chills, aches, pains",        # patient 2 
    +              "fever",                       # patient 3
    +              "vomiting, diarrhoea",         # patient 4
    +              "bleeding from gums, fever",   # patient 5
    +              "rapid pulse, headache")       # patient 6
    +
    +str_split(symptoms, ",")                     # split each patient's symptoms
    +
    +
    [[1]]
    +[1] "jaundice" " fever"   " chills" 
    +
    +[[2]]
    +[1] "chills" " aches" " pains"
    +
    +[[3]]
    +[1] "fever"
    +
    +[[4]]
    +[1] "vomiting"   " diarrhoea"
    +
    +[[5]]
    +[1] "bleeding from gums" " fever"            
    +
    +[[6]]
    +[1] "rapid pulse" " headache"  
    +
    +
    +

    To return a “character matrix” instead, which may be useful if creating data frame columns, set the argument simplify = TRUE as shown below:

    +
    +
    str_split(symptoms, ",", simplify = TRUE)
    +
    +
         [,1]                 [,2]         [,3]     
    +[1,] "jaundice"           " fever"     " chills"
    +[2,] "chills"             " aches"     " pains" 
    +[3,] "fever"              ""           ""       
    +[4,] "vomiting"           " diarrhoea" ""       
    +[5,] "bleeding from gums" " fever"     ""       
    +[6,] "rapid pulse"        " headache"  ""       
    +
    +
    +

    You can also adjust the number of splits to create with the n = argument. For example, this restricts the number of splits to 2. Any further commas remain within the second values.

    +
    +
    str_split(symptoms, ",", simplify = TRUE, n = 2)
    +
    +
         [,1]                 [,2]            
    +[1,] "jaundice"           " fever, chills"
    +[2,] "chills"             " aches, pains" 
    +[3,] "fever"              ""              
    +[4,] "vomiting"           " diarrhoea"    
    +[5,] "bleeding from gums" " fever"        
    +[6,] "rapid pulse"        " headache"     
    +
    +
    +

    Note - the same outputs can be achieved with str_split_fixed(), in which you do not give the simplify argument, but must instead designate the number of columns (n).

    +
    +
    str_split_fixed(symptoms, ",", n = 2)
    +
    +
    +
    +

    Split columns

    +

    If you are trying to split data frame column, it is best to use the separate() function from dplyr. It is used to split one character column into other columns.

    +

    Let’s say we have a simple data frame df (defined and united in the unite section) containing a case_ID column, one character column with many symptoms, and one outcome column. Our goal is to separate the symptoms column into many columns - each one containing one symptom.

    +
    +
    +
    + +
    +
    +

    Assuming the data are piped into separate(), first provide the column to be separated. Then provide into = as a vector c( ) containing the new columns names, as shown below.

    +
      +
    • sep = the separator, can be a character, or a number (interpreted as the character position to split at).
    • +
    • remove = FALSE by default, removes the input column.
      +
    • +
    • convert = FALSE by default, will cause string “NA”s to become NA.
      +
    • +
    • extra = this controls what happens if there are more values created by the separation than new columns named. +
        +
      • extra = "warn" means you will see a warning but it will drop excess values (the default).
        +
      • +
      • extra = "drop" means the excess values will be dropped with no warning.
        +
      • +
      • extra = "merge" will only split to the number of new columns listed in into - this setting will preserve all your data.
      • +
    • +
    +

    An example with extra = "merge" is below - no data is lost. Two new columns are defined but any third symptoms are left in the second new column:

    +
    +
    # third symptoms combined into second new column
    +df %>% 
    +  separate(symptoms, into = c("sym_1", "sym_2"), sep=",", extra = "merge")
    +
    +
    Warning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].
    +
    +
    +
      case_ID              sym_1          sym_2 outcome
    +1       1           jaundice  fever, chills Recover
    +2       2             chills   aches, pains   Death
    +3       3              fever           <NA>   Death
    +4       4           vomiting      diarrhoea Recover
    +5       5 bleeding from gums          fever Recover
    +6       6        rapid pulse       headache Recover
    +
    +
    +

    When the default extra = "drop" is used below, a warning is given but the third symptoms are lost:

    +
    +
    # third symptoms are lost
    +df %>% 
    +  separate(symptoms, into = c("sym_1", "sym_2"), sep=",")
    +
    +
    Warning: Expected 2 pieces. Additional pieces discarded in 2 rows [1, 2].
    +
    +
    +
    Warning: Expected 2 pieces. Missing pieces filled with `NA` in 1 rows [3].
    +
    +
    +
      case_ID              sym_1      sym_2 outcome
    +1       1           jaundice      fever Recover
    +2       2             chills      aches   Death
    +3       3              fever       <NA>   Death
    +4       4           vomiting  diarrhoea Recover
    +5       5 bleeding from gums      fever Recover
    +6       6        rapid pulse   headache Recover
    +
    +
    +

    CAUTION: If you do not provide enough into values for the new columns, your data may be truncated.

    + +
    +
    +

    Arrange alphabetically

    +

    Several strings can be sorted by alphabetical order. str_order() returns the order, while str_sort() returns the strings in that order.

    +
    +
    # strings
    +health_zones <- c("Alba", "Takota", "Delta")
    +
    +# return the alphabetical order
    +str_order(health_zones)
    +
    +
    [1] 1 3 2
    +
    +
    # return the strings in alphabetical order
    +str_sort(health_zones)
    +
    +
    [1] "Alba"   "Delta"  "Takota"
    +
    +
    +

    To use a different alphabet, add the argument locale =. See the full list of locales by entering stringi::stri_locale_list() in the R console.

    + +
    +
    +

    base R functions

    +

    It is common to see base R functions paste() and paste0(), which concatenate vectors after converting all parts to character. They act similarly to str_c() but the syntax is arguably more complicated - in the parentheses each part is separated by a comma. The parts are either character text (in quotes) or pre-defined code objects (no quotes). For example:

    +
    +
    n_beds <- 10
    +n_masks <- 20
    +
    +paste0("Regional hospital needs ", n_beds, " beds and ", n_masks, " masks.")
    +
    +
    [1] "Regional hospital needs 10 beds and 20 masks."
    +
    +
    +

    sep = and collapse = arguments can be specified. paste() is simply paste0() with a default sep = " " (one space).

    +
    +
    +
    +

    10.3 Clean and standardise

    + +
    +

    Change case

    +

    Often one must alter the case/capitalization of a string value, for example names of jursidictions. Use str_to_upper(), str_to_lower(), and str_to_title(), from stringr, as shown below:

    +
    +
    str_to_upper("California")
    +
    +
    [1] "CALIFORNIA"
    +
    +
    str_to_lower("California")
    +
    +
    [1] "california"
    +
    +
    +

    Using *base** R, the above can also be achieved with toupper(), tolower().

    +

    Title case

    +

    Transforming the string so each word is capitalized can be achieved with str_to_title():

    +
    +
    str_to_title("go to the US state of california ")
    +
    +
    [1] "Go To The Us State Of California "
    +
    +
    +

    Use toTitleCase() from the tools package to achieve more nuanced capitalization (words like “to”, “the”, and “of” are not capitalized).

    +
    +
    tools::toTitleCase("This is the US state of california")
    +
    +
    [1] "This is the US State of California"
    +
    +
    +

    You can also use str_to_sentence(), which capitalizes only the first letter of the string.

    +
    +
    str_to_sentence("the patient must be transported")
    +
    +
    [1] "The patient must be transported"
    +
    +
    +
    +
    +

    Pad length

    +

    Use str_pad() to add characters to a string, to a minimum length. By default spaces are added, but you can also pad with other characters using the pad = argument.

    +
    +
    # ICD codes of differing length
    +ICD_codes <- c("R10.13",
    +               "R10.819",
    +               "R17")
    +
    +# ICD codes padded to 7 characters on the right side
    +str_pad(ICD_codes, 7, "right")
    +
    +
    [1] "R10.13 " "R10.819" "R17    "
    +
    +
    # Pad with periods instead of spaces
    +str_pad(ICD_codes, 7, "right", pad = ".")
    +
    +
    [1] "R10.13." "R10.819" "R17...."
    +
    +
    +

    For example, to pad numbers with leading zeros (such as for hours or minutes), you can pad the number to minimum length of 2 with pad = "0".

    +
    +
    # Add leading zeros to two digits (e.g. for times minutes/hours)
    +str_pad("4", 2, pad = "0") 
    +
    +
    [1] "04"
    +
    +
    # example using a numeric column named "hours"
    +# hours <- str_pad(hours, 2, pad = "0")
    +
    +
    +
    +

    Truncate

    +

    str_trunc() sets a maximum length for each string. If a string exceeds this length, it is truncated (shortened) and an ellipsis (…) is included to indicate that the string was previously longer. Note that the ellipsis is counted in the length. The ellipsis characters can be changed with the argument ellipsis =. The optional side = argument specifies which where the ellipsis will appear within the truncated string (“left”, “right”, or “center”).

    +
    +
    original <- "Symptom onset on 4/3/2020 with vomiting"
    +str_trunc(original, 10, "center")
    +
    +
    [1] "Symp...ing"
    +
    +
    +
    +
    +

    Standardize length

    +

    Use str_trunc() to set a maximum length, and then use str_pad() to expand the very short strings to that truncated length. In the example below, 6 is set as the maximum length (one value is truncated), and then one very short value is padded to achieve length of 6.

    +
    +
    # ICD codes of differing length
    +ICD_codes   <- c("R10.13",
    +                 "R10.819",
    +                 "R17")
    +
    +# truncate to maximum length of 6
    +ICD_codes_2 <- str_trunc(ICD_codes, 6)
    +ICD_codes_2
    +
    +
    [1] "R10.13" "R10..." "R17"   
    +
    +
    # expand to minimum length of 6
    +ICD_codes_3 <- str_pad(ICD_codes_2, 6, "right")
    +ICD_codes_3
    +
    +
    [1] "R10.13" "R10..." "R17   "
    +
    +
    +
    +
    +

    Remove leading/trailing whitespace

    +

    Use str_trim() to remove spaces, newlines (\n) or tabs (\t) on sides of a string input. Add "right" "left", or "both" to the command to specify which side to trim (e.g. str_trim(x, "right").

    +
    +
    # ID numbers with excess spaces on right
    +IDs <- c("provA_1852  ", # two excess spaces
    +         "provA_2345",   # zero excess spaces
    +         "provA_9460 ")  # one excess space
    +
    +# IDs trimmed to remove excess spaces on right side only
    +str_trim(IDs)
    +
    +
    [1] "provA_1852" "provA_2345" "provA_9460"
    +
    +
    +
    +
    +

    Remove repeated whitespace within

    +

    Use str_squish() to remove repeated spaces that appear inside a string. For example, to convert double spaces into single spaces. It also removes spaces, newlines, or tabs on the outside of the string like str_trim().

    +
    +
    # original contains excess spaces within string
    +str_squish("  Pt requires   IV saline\n") 
    +
    +
    [1] "Pt requires IV saline"
    +
    +
    +

    Enter ?str_trim, ?str_pad in your R console to see further details.

    +
    +
    +

    Wrap into paragraphs

    +

    Use str_wrap() to wrap a long unstructured text into a structured paragraph with fixed line length. Provide the ideal character length for each line, and it applies an algorithm to insert newlines (\n) within the paragraph, as seen in the example below.

    +
    +
    pt_course <- "Symptom onset 1/4/2020 vomiting chills fever. Pt saw traditional healer in home village on 2/4/2020. On 5/4/2020 pt symptoms worsened and was admitted to Lumta clinic. Sample was taken and pt was transported to regional hospital on 6/4/2020. Pt died at regional hospital on 7/4/2020."
    +
    +str_wrap(pt_course, 40)
    +
    +
    [1] "Symptom onset 1/4/2020 vomiting chills\nfever. Pt saw traditional healer in\nhome village on 2/4/2020. On 5/4/2020\npt symptoms worsened and was admitted\nto Lumta clinic. Sample was taken and pt\nwas transported to regional hospital on\n6/4/2020. Pt died at regional hospital\non 7/4/2020."
    +
    +
    +

    The base function cat() can be wrapped around the above command in order to print the output, displaying the new lines added.

    +
    +
    cat(str_wrap(pt_course, 40))
    +
    +
    Symptom onset 1/4/2020 vomiting chills
    +fever. Pt saw traditional healer in
    +home village on 2/4/2020. On 5/4/2020
    +pt symptoms worsened and was admitted
    +to Lumta clinic. Sample was taken and pt
    +was transported to regional hospital on
    +6/4/2020. Pt died at regional hospital
    +on 7/4/2020.
    +
    +
    + +
    +
    +
    +

    10.4 Handle by position

    +
    +

    Extract by character position

    +

    Use str_sub() to return only a part of a string. The function takes three main arguments:

    +
      +
    1. The character vector(s)
    2. +
    3. Start position
    4. +
    5. End position
    6. +
    +

    A few notes on position numbers:

    +
      +
    • If a position number is positive, the position is counted starting from the left end of the string
      +
    • +
    • If a position number is negative, it is counted starting from the right end of the string
    • +
    • Position numbers are inclusive
    • +
    • Positions extending beyond the string will be truncated (removed)
    • +
    +

    Below are some examples applied to the string “pneumonia”:

    +
    +
    # start and end third from left (3rd letter from left)
    +str_sub("pneumonia", 3, 3)
    +
    +
    [1] "e"
    +
    +
    # 0 is not present
    +str_sub("pneumonia", 0, 0)
    +
    +
    [1] ""
    +
    +
    # 6th from left, to the 1st from right
    +str_sub("pneumonia", 6, -1)
    +
    +
    [1] "onia"
    +
    +
    # 5th from right, to the 2nd from right
    +str_sub("pneumonia", -5, -2)
    +
    +
    [1] "moni"
    +
    +
    # 4th from left to a position outside the string
    +str_sub("pneumonia", 4, 15)
    +
    +
    [1] "umonia"
    +
    +
    +
    +
    +

    Extract by word position

    +

    To extract the nth ‘word’, use word(), also from stringr. Provide the string(s), then the first word position to extract, and the last word position to extract.

    +

    By default, the separator between ‘words’ is assumed to be a space, unless otherwise indicated with sep = (e.g. sep = "_" when words are separated by underscores.

    +
    +
    # strings to evaluate
    +chief_complaints <- c("I just got out of the hospital 2 days ago, but still can barely breathe.",
    +                      "My stomach hurts",
    +                      "Severe ear pain")
    +
    +# extract 1st to 3rd words of each string
    +word(chief_complaints, start = 1, end = 3, sep = " ")
    +
    +
    [1] "I just got"       "My stomach hurts" "Severe ear pain" 
    +
    +
    +
    +
    +

    Replace by character position

    +

    str_sub() paired with the assignment operator (<-) can be used to modify a part of a string:

    +
    +
    word <- "pneumonia"
    +
    +# convert the third and fourth characters to X 
    +str_sub(word, 3, 4) <- "XX"
    +
    +# print
    +word
    +
    +
    [1] "pnXXmonia"
    +
    +
    +

    An example applied to multiple strings (e.g. a column). Note the expansion in length of “HIV”.

    +
    +
    words <- c("pneumonia", "tubercolosis", "HIV")
    +
    +# convert the third and fourth characters to X 
    +str_sub(words, 3, 4) <- "XX"
    +
    +words
    +
    +
    [1] "pnXXmonia"    "tuXXrcolosis" "HIXX"        
    +
    +
    +
    +
    +

    Evaluate length

    +
    +
    str_length("abc")
    +
    +
    [1] 3
    +
    +
    +

    Alternatively, use nchar() from base R

    + +
    +
    +
    +

    10.5 Patterns

    +

    Many stringr functions work to detect, locate, extract, match, replace, and split based on a specified pattern.

    + +
    +

    Detect a pattern

    +

    Use str_detect() as below to detect presence/absence of a pattern within a string. First provide the string or vector to search in (string =), and then the pattern to look for (pattern =). Note that by default the search is case sensitive!

    +
    +
    str_detect(string = "primary school teacher", pattern = "teach")
    +
    +
    [1] TRUE
    +
    +
    +

    The argument negate = can be included and set to TRUE if you want to know if the pattern is NOT present.

    +
    +
    str_detect(string = "primary school teacher", pattern = "teach", negate = TRUE)
    +
    +
    [1] FALSE
    +
    +
    +

    To ignore case/capitalization, wrap the pattern within regex(), and within regex() add the argument ignore_case = TRUE (or T as shorthand).

    +
    +
    str_detect(string = "Teacher", pattern = regex("teach", ignore_case = T))
    +
    +
    [1] TRUE
    +
    +
    +

    When str_detect() is applied to a character vector or a data frame column, it will return TRUE or FALSE for each of the values.

    +
    +
    # a vector/column of occupations 
    +occupations <- c("field laborer",
    +                 "university professor",
    +                 "primary school teacher & tutor",
    +                 "tutor",
    +                 "nurse at regional hospital",
    +                 "lineworker at Amberdeen Fish Factory",
    +                 "physican",
    +                 "cardiologist",
    +                 "office worker",
    +                 "food service")
    +
    +# Detect presence of pattern "teach" in each string - output is vector of TRUE/FALSE
    +str_detect(occupations, "teach")
    +
    +
     [1] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
    +
    +
    +

    If you need to count the TRUEs, simply sum() the output. This counts the number TRUE.

    +
    +
    sum(str_detect(occupations, "teach"))
    +
    +
    [1] 1
    +
    +
    +

    To search inclusive of multiple terms, include them separated by OR bars (|) within the pattern = argument, as shown below:

    +
    +
    sum(str_detect(string = occupations, pattern = "teach|professor|tutor"))
    +
    +
    [1] 3
    +
    +
    +

    If you need to build a long list of search terms, you can combine them using str_c() and sep = |, then define this is a character object, and then reference the vector later more succinctly. The example below includes possible occupation search terms for front-line medical providers.

    +
    +
    # search terms
    +occupation_med_frontline <- str_c("medical", "medicine", "hcw", "healthcare", "home care", "home health",
    +                                "surgeon", "doctor", "doc", "physician", "surgery", "peds", "pediatrician",
    +                               "intensivist", "cardiologist", "coroner", "nurse", "nursing", "rn", "lpn",
    +                               "cna", "pa", "physician assistant", "mental health",
    +                               "emergency department technician", "resp therapist", "respiratory",
    +                                "phlebotomist", "pharmacy", "pharmacist", "hospital", "snf", "rehabilitation",
    +                               "rehab", "activity", "elderly", "subacute", "sub acute",
    +                                "clinic", "post acute", "therapist", "extended care",
    +                                "dental", "dential", "dentist", sep = "|")
    +
    +occupation_med_frontline
    +
    +
    [1] "medical|medicine|hcw|healthcare|home care|home health|surgeon|doctor|doc|physician|surgery|peds|pediatrician|intensivist|cardiologist|coroner|nurse|nursing|rn|lpn|cna|pa|physician assistant|mental health|emergency department technician|resp therapist|respiratory|phlebotomist|pharmacy|pharmacist|hospital|snf|rehabilitation|rehab|activity|elderly|subacute|sub acute|clinic|post acute|therapist|extended care|dental|dential|dentist"
    +
    +
    +

    This command returns the number of occupations which contain any one of the search terms for front-line medical providers (occupation_med_frontline):

    +
    +
    sum(str_detect(string = occupations, pattern = occupation_med_frontline))
    +
    +
    [1] 2
    +
    +
    +

    Base R string search functions

    +

    The base function grepl() works similarly to str_detect(), in that it searches for matches to a pattern and returns a logical vector. The basic syntax is grepl(pattern, strings_to_search, ignore.case = FALSE, ...). One advantage is that the ignore.case argument is easier to write (there is no need to involve the regex() function).

    +

    Likewise, the base functions sub() and gsub() act similarly to str_replace(). Their basic syntax is: gsub(pattern, replacement, strings_to_search, ignore.case = FALSE). sub() will replace the first instance of the pattern, whereas gsub() will replace all instances of the pattern.

    +
    +

    Convert commas to periods

    +

    Here is an example of using gsub() to convert commas to periods in a vector of numbers. This could be useful if your data come from parts of the world other than the United States or Great Britain.

    +

    The inner gsub() which acts first on lengths is converting any periods to no space ““. The period character”.” has to be “escaped” with two slashes to actually signify a period, because “.” in regex means “any character”. Then, the result (with only commas) is passed to the outer gsub() in which commas are replaced by periods.

    +
    +
    lengths <- c("2.454,56", "1,2", "6.096,5")
    +
    +as.numeric(gsub(pattern = ",",                # find commas     
    +                replacement = ".",            # replace with periods
    +                x = gsub("\\.", "", lengths)  # vector with other periods removed (periods escaped)
    +                )
    +           )                                  # convert outcome to numeric
    +
    +
    +
    +
    +

    Replace all

    +

    Use str_replace_all() as a “find and replace” tool. First, provide the strings to be evaluated to string =, then the pattern to be replaced to pattern =, and then the replacement value to replacement =. The example below replaces all instances of “dead” with “deceased”. Note, this IS case sensitive.

    +
    +
    outcome <- c("Karl: dead",
    +            "Samantha: dead",
    +            "Marco: not dead")
    +
    +str_replace_all(string = outcome, pattern = "dead", replacement = "deceased")
    +
    +
    [1] "Karl: deceased"      "Samantha: deceased"  "Marco: not deceased"
    +
    +
    +

    Notes:

    +
      +
    • To replace a pattern with NA, use str_replace_na().
      +
    • +
    • The function str_replace() replaces only the first instance of the pattern within each evaluated string.
    • +
    + +
    +
    +

    Detect within logic

    +

    Within case_when()

    +

    str_detect() is often used within case_when() (from dplyr). Let’s say occupations is a column in the linelist. The mutate() below creates a new column called is_educator by using conditional logic via case_when(). See the page on data cleaning to learn more about case_when().

    +
    +
    df <- df %>% 
    +  mutate(is_educator = case_when(
    +    # term search within occupation, not case sensitive
    +    str_detect(occupations,
    +               regex("teach|prof|tutor|university",
    +                     ignore_case = TRUE))              ~ "Educator",
    +    # all others
    +    TRUE                                               ~ "Not an educator"))
    +
    +

    As a reminder, it may be important to add exclusion criteria to the conditional logic (negate = F):

    +
    +
    df <- df %>% 
    +  # value in new column is_educator is based on conditional logic
    +  mutate(is_educator = case_when(
    +    
    +    # occupation column must meet 2 criteria to be assigned "Educator":
    +    # it must have a search term AND NOT any exclusion term
    +    
    +    # Must have a search term
    +    str_detect(occupations,
    +               regex("teach|prof|tutor|university", ignore_case = T)) &              
    +    
    +    # AND must NOT have an exclusion term
    +    str_detect(occupations,
    +               regex("admin", ignore_case = T),
    +               negate = TRUE                        ~ "Educator"
    +    
    +    # All rows not meeting above criteria
    +    TRUE                                            ~ "Not an educator"))
    +
    + +
    +
    +

    Locate pattern position

    +

    To locate the first position of a pattern, use str_locate(). It outputs a start and end position.

    +
    +
    str_locate("I wish", "sh")
    +
    +
         start end
    +[1,]     5   6
    +
    +
    +

    Like other str functions, there is an “_all” version (str_locate_all()) which will return the positions of all instances of the pattern within each string. This outputs as a list.

    +
    +
    phrases <- c("I wish", "I hope", "he hopes", "He hopes")
    +
    +str_locate(phrases, "h" )     # position of *first* instance of the pattern
    +
    +
         start end
    +[1,]     6   6
    +[2,]     3   3
    +[3,]     1   1
    +[4,]     4   4
    +
    +
    str_locate_all(phrases, "h" ) # position of *every* instance of the pattern
    +
    +
    [[1]]
    +     start end
    +[1,]     6   6
    +
    +[[2]]
    +     start end
    +[1,]     3   3
    +
    +[[3]]
    +     start end
    +[1,]     1   1
    +[2,]     4   4
    +
    +[[4]]
    +     start end
    +[1,]     4   4
    +
    +
    + +
    +
    +

    Extract a match

    +

    str_extract_all() returns the matching patterns themselves, which is most useful when you have offered several patterns via “OR” conditions. For example, looking in the string vector of occupations (see previous tab) for either “teach”, “prof”, or “tutor”.

    +

    str_extract_all() returns a list which contains all matches for each evaluated string. See below how occupation 3 has two pattern matches within it.

    +
    +
    str_extract_all(occupations, "teach|prof|tutor")
    +
    +
    [[1]]
    +character(0)
    +
    +[[2]]
    +[1] "prof"
    +
    +[[3]]
    +[1] "teach" "tutor"
    +
    +[[4]]
    +[1] "tutor"
    +
    +[[5]]
    +character(0)
    +
    +[[6]]
    +character(0)
    +
    +[[7]]
    +character(0)
    +
    +[[8]]
    +character(0)
    +
    +[[9]]
    +character(0)
    +
    +[[10]]
    +character(0)
    +
    +
    +

    str_extract() extracts only the first match in each evaluated string, producing a character vector with one element for each evaluated string. It returns NA where there was no match. The NAs can be removed by wrapping the returned vector with na.exclude(). Note how the second of occupation 3’s matches is not shown.

    +
    +
    str_extract(occupations, "teach|prof|tutor")
    +
    +
     [1] NA      "prof"  "teach" "tutor" NA      NA      NA      NA      NA     
    +[10] NA     
    +
    +
    + +
    +
    +

    Subset and count

    +

    Aligned functions include str_subset() and str_count().

    +

    str_subset() returns the actual values which contained the pattern:

    +
    +
    str_subset(occupations, "teach|prof|tutor")
    +
    +
    [1] "university professor"           "primary school teacher & tutor"
    +[3] "tutor"                         
    +
    +
    +

    str_count() returns a vector of numbers: the number of times a search term appears in each evaluated value.

    +
    +
    str_count(occupations, regex("teach|prof|tutor", ignore_case = TRUE))
    +
    +
     [1] 0 1 2 1 0 0 0 0 0 0
    +
    +
    + +
    +
    +
    +

    10.6 Special characters

    +

    Backslash \ as escape

    +

    The backslash \ is used to “escape” the meaning of the next character. This way, a backslash can be used to have a quote mark display within other quote marks (\") - the middle quote mark will not “break” the surrounding quote marks.

    +

    Note - thus, if you want to display a backslash, you must escape it’s meaning with another backslash. So you must write two backslashes \\ to display one.

    +

    Special characters

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Special characterRepresents
    "\\"backslash
    "\n"a new line (newline)
    "\""double-quote within double quotes
    '\''single-quote within single quotes
    "\| grave accent| carriage return| tab| vertical tab“`backspace
    +

    Run ?"'" in the R Console to display a complete list of these special characters (it will appear in the RStudio Help pane).

    + +
    +
    +

    10.7 Regular expressions (regex) and special characters

    +

    Regular expressions, or “regex”, is a concise language for describing patterns in strings. If you are not familiar with it, a regular expression can look like an alien language. Here we try to de-mystify this language a little bit.

    +

    Much of this section is adapted from this tutorial and this cheatsheet. We selectively adapt here knowing that this handbook might be viewed by people without internet access to view the other tutorials.

    +

    A regular expression is often applied to extract specific patterns from “unstructured” text - for example medical notes, chief complaints, patient history, or other free text columns in a data frame

    +

    There are four basic tools one can use to create a basic regular expression:

    +
      +
    1. Character sets
    2. +
    3. Meta characters
    4. +
    5. Quantifiers
    6. +
    7. Groups
    8. +
    +

    Character sets

    +

    Character sets, are a way of expressing listing options for a character match, within brackets. So any a match will be triggered if any of the characters within the brackets are found in the string. For example, to look for vowels one could use this character set: “[aeiou]”. Some other common character sets are:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Character setMatches for
    "[A-Z]"any single capital letter
    "[a-z]"any single lowercase letter
    "[0-9]"any digit
    [:alnum:]any alphanumeric character
    [:digit:]any numeric digit
    [:alpha:]any letter (upper or lowercase)
    [:upper:]any uppercase letter
    [:lower:]any lowercase letter
    +

    Character sets can be combined within one bracket (no spaces!), such as "[A-Za-z]" (any upper or lowercase letter), or another example "[t-z0-5]" (lowercase t through z OR number 0 through 5).

    +

    Meta characters

    +

    Meta characters are shorthand for character sets. Some of the important ones are listed below:

    + ++++ + + + + + + + + + + + + + + + + + + + + +
    Meta characterRepresents
    "\\s"a single space
    "\\w"any single alphanumeric character (A-Z, a-z, or 0-9)
    "\\d"any single numeric digit (0-9)
    +

    Quantifiers

    +

    Typically you do not want to search for a match on only one character. Quantifiers allow you to designate the length of letters/numbers to allow for the match.

    +

    Quantifiers are numbers written within curly brackets { } after the character they are quantifying, for example:

    +
      +
    • "A{2}" will return instances of two capital A letters
    • +
    • "A{2,4}" will return instances of between two and four capital A letters (do not put spaces!)
    • +
    • "A{2,}" will return instances of two or more capital A letters
    • +
    • "A+" will return instances of one or more capital A letters (group extended until a different character is encountered)
    • +
    • Precede with an * asterisk to return zero or more matches (useful if you are not sure the pattern is present)
    • +
    +

    Using the + plus symbol as a quantifier, the match will occur until a different character is encountered. For example, this expression will return all words (alpha characters: "[A-Za-z]+"

    +
    +
    # test string for quantifiers
    +test <- "A-AA-AAA-AAAA"
    +
    +

    When a quantifier of {2} is used, only pairs of consecutive A’s are returned. Two pairs are identified within AAAA.

    +
    +
    str_extract_all(test, "A{2}")
    +
    +
    [[1]]
    +[1] "AA" "AA" "AA" "AA"
    +
    +
    +

    When a quantifier of {2,4} is used, groups of consecutive A’s that are two to four in length are returned.

    +
    +
    str_extract_all(test, "A{2,4}")
    +
    +
    [[1]]
    +[1] "AA"   "AAA"  "AAAA"
    +
    +
    +

    With the quantifier +, groups of one or more are returned:

    +
    +
    str_extract_all(test, "A+")
    +
    +
    [[1]]
    +[1] "A"    "AA"   "AAA"  "AAAA"
    +
    +
    +

    Relative position

    +

    These express requirements for what precedes or follows a pattern. For example, to extract sentences, “two numbers that are followed by a period” (""). (?<=\.)\s(?=[A-Z])

    +
    +
    str_extract_all(test, "")
    +
    +
    [[1]]
    + [1] "A" "-" "A" "A" "-" "A" "A" "A" "-" "A" "A" "A" "A"
    +
    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    Position statementMatches to
    "(?<=b)a"“a” that is preceded by a “b”
    "(?<!b)a"“a” that is NOT preceded by a “b”
    "a(?=b)"“a” that is followed by a “b”
    "a(?!b)"“a” that is NOT followed by a “b”
    +

    Groups

    +

    Capturing groups in your regular expression is a way to have a more organized output upon extraction.

    +

    Regex examples

    +

    Below is a free text for the examples. We will try to extract useful information from it using a regular expression search term.

    +
    +
    pt_note <- "Patient arrived at Broward Hospital emergency ward at 18:00 on 6/12/2005. Patient presented with radiating abdominal pain from LR quadrant. Patient skin was pale, cool, and clammy. Patient temperature was 99.8 degrees farinheit. Patient pulse rate was 100 bpm and thready. Respiratory rate was 29 per minute."
    +
    +

    This expression matches to all words (any character until hitting non-character such as a space):

    +
    +
    str_extract_all(pt_note, "[A-Za-z]+")
    +
    +
    [[1]]
    + [1] "Patient"     "arrived"     "at"          "Broward"     "Hospital"   
    + [6] "emergency"   "ward"        "at"          "on"          "Patient"    
    +[11] "presented"   "with"        "radiating"   "abdominal"   "pain"       
    +[16] "from"        "LR"          "quadrant"    "Patient"     "skin"       
    +[21] "was"         "pale"        "cool"        "and"         "clammy"     
    +[26] "Patient"     "temperature" "was"         "degrees"     "farinheit"  
    +[31] "Patient"     "pulse"       "rate"        "was"         "bpm"        
    +[36] "and"         "thready"     "Respiratory" "rate"        "was"        
    +[41] "per"         "minute"     
    +
    +
    +

    The expression "[0-9]{1,2}" matches to consecutive numbers that are 1 or 2 digits in length. It could also be written "\\d{1,2}", or "[:digit:]{1,2}".

    +
    +
    str_extract_all(pt_note, "[0-9]{1,2}")
    +
    +
    [[1]]
    + [1] "18" "00" "6"  "12" "20" "05" "99" "8"  "10" "0"  "29"
    +
    +
    + + + + +

    You can view a useful list of regex expressions and tips on page 2 of this cheatsheet

    +

    Also see this tutorial.

    +

    Additionally, the package RVerbalExpressions can offer an easy way to construct regular expressions.

    +
    +
    devtools::install_github("VerbalExpressions/RVerbalExpressions")
    +
    + +
    +
    +

    10.8 Resources

    +

    A reference sheet for stringr functions can be found here

    +

    A vignette on stringr can be found here

    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/characters_strings.qmd b/new_pages/characters_strings.qmd index ced92587..8cf3834f 100644 --- a/new_pages/characters_strings.qmd +++ b/new_pages/characters_strings.qmd @@ -9,14 +9,14 @@ knitr::include_graphics(here::here("images", "Characters_Strings_1500x500.png")) This page demonstrates use of the **stringr** package to evaluate and handle character values ("strings"). -1. Combine, order, split, arrange - `str_c()`, `str_glue()`, `str_order()`, `str_split()` -2. Clean and standardise - * Adjust length - `str_pad()`, `str_trunc()`, `str_wrap()` - * Change case - `str_to_upper()`, `str_to_title()`, `str_to_lower()`, `str_to_sentence()` -3. Evaluate and extract by position - `str_length()`, `str_sub()`, `word()` -4. Patterns - * Detect and locate - `str_detect()`, `str_subset()`, `str_match()`, `str_extract()` - * Modify and replace - `str_sub()`, `str_replace_all()` +1. Combine, order, split, arrange - `str_c()`, `str_glue()`, `str_order()`, `str_split()` +2. Clean and standardise + * Adjust length - `str_pad()`, `str_trunc()`, `str_wrap()` + * Change case - `str_to_upper()`, `str_to_title()`, `str_to_lower()`, `str_to_sentence()` +3. Evaluate and extract by position - `str_length()`, `str_sub()`, `word()` +4. Patterns + * Detect and locate - `str_detect()`, `str_subset()`, `str_match()`, `str_extract()` + * Modify and replace - `str_sub()`, `str_replace_all()` 7. Regular expressions ("regex") @@ -38,7 +38,8 @@ Install or load the **stringr** and other **tidyverse** packages. pacman::p_load( stringr, # many functions for handling strings tidyverse, # for optional data manipulation - tools) # alternative for converting to title case + tools # alternative for converting to title case + ) ``` @@ -46,9 +47,9 @@ pacman::p_load( ### Import data {.unnumbered} -In this page we will occassionally reference the cleaned `linelist` of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd)(importing.qmd) page for details). +In this page we will occassionally reference the cleaned `linelist` of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -74,9 +75,9 @@ DT::datatable(head(linelist, 50), rownames = FALSE, filter="top", options = list This section covers: -* Using `str_c()`, `str_glue()`, and `unite()` to combine strings -* Using `str_order()` to arrange strings -* Using `str_split()` and `separate()` to split strings +* Using `str_c()`, `str_glue()`, and `unite()` to combine strings +* Using `str_order()` to arrange strings +* Using `str_split()` and `separate()` to split strings @@ -125,11 +126,11 @@ cat(str_c(first_names, last_names, sep = " ", collapse = ";\n")) Use `str_glue()` to insert dynamic R code into a string. This is a very useful function for creating dynamic plot captions, as demonstrated below. -* All content goes between double quotation marks `str_glue("")` +* All content goes between double quotation marks `str_glue("")`. * Any dynamic code or references to pre-defined values are placed within curly brackets `{}` within the double quotation marks. There can be many curly brackets in the same `str_glue()` command. -* To display character quotes '', use *single* quotes within the surrounding double quotes (e.g. when providing date format - see example below) -* Tip: You can use `\n` to force a new line -* Tip: You use `format()` to adjust date display, and use `Sys.Date()` to display the current date +* To display character quotes '', use *single* quotes within the surrounding double quotes (e.g. when providing date format - see example below). +* Tip: You can use `\n` to force a new line. +* Tip: You use `format()` to adjust date display, and use `Sys.Date()` to display the current date. A simple example, of a dynamic plot caption: @@ -203,8 +204,8 @@ Within a data frame, bringing together character values from multiple columns ca Provide the name of the new united column. Then provide the names of the columns you wish to unite. * By default, the separator used in the united column is underscore `_`, but this can be changed with the `sep = ` argument. -* `remove = ` removes the input columns from the data frame (TRUE by default) -* `na.rm = ` removes missing values while uniting (FALSE by default) +* `remove = ` removes the input columns from the data frame (TRUE by default). +* `na.rm = ` removes missing values while uniting (FALSE by default). Below, we define a mini-data frame to demonstrate with: @@ -320,13 +321,13 @@ DT::datatable(df, rownames = FALSE, options = list(pageLength = 5, scrollX=T), c Assuming the data are piped into `separate()`, first provide the column to be separated. Then provide `into = ` as a vector `c( )` containing the *new* columns names, as shown below. -* `sep = ` the separator, can be a character, or a number (interpreted as the character position to split at) -* `remove = ` FALSE by default, removes the input column -* `convert = ` FALSE by default, will cause string "NA"s to become `NA` +* `sep = ` the separator, can be a character, or a number (interpreted as the character position to split at). +* `remove = ` FALSE by default, removes the input column. +* `convert = ` FALSE by default, will cause string "NA"s to become `NA`. * `extra = ` this controls what happens if there are more values created by the separation than new columns named. - * `extra = "warn"` means you will see a warning but it will drop excess values (**the default**) - * `extra = "drop"` means the excess values will be dropped with no warning - * **`extra = "merge"` will only split to the number of new columns listed in `into` - *this setting will preserve all your data*** + * `extra = "warn"` means you will see a warning but it will drop excess values (**the default**). + * `extra = "drop"` means the excess values will be dropped with no warning. + * **`extra = "merge"` will only split to the number of new columns listed in `into` - *this setting will preserve all your data***. An example with `extra = "merge"` is below - no data is lost. Two new columns are defined but any third symptoms are left in the second new column: @@ -555,16 +556,16 @@ cat(str_wrap(pt_course, 40)) Use `str_sub()` to return only a part of a string. The function takes three main arguments: -1) the character vector(s) -2) start position -3) end position +1) The character vector(s) +2) Start position +3) End position A few notes on position numbers: -* If a position number is positive, the position is counted starting from the left end of the string. -* If a position number is negative, it is counted starting from the right end of the string. -* Position numbers are inclusive. -* Positions extending beyond the string will be truncated (removed). +* If a position number is positive, the position is counted starting from the left end of the string +* If a position number is negative, it is counted starting from the right end of the string +* Position numbers are inclusive +* Positions extending beyond the string will be truncated (removed) Below are some examples applied to the string "pneumonia": @@ -888,22 +889,6 @@ str_count(occupations, regex("teach|prof|tutor", ignore_case = TRUE)) - - - - - - - -### Regex groups {.unnumbered} - -UNDER CONSTRUCTION - - - - - - ## Special characters @@ -933,11 +918,7 @@ Run `?"'"` in the R Console to display a complete list of these special characte -## Regular expressions (regex) - - - -## Regex and special characters { } +## Regular expressions (regex) and special characters { } Regular expressions, or "regex", is a concise language for describing patterns in strings. If you are not familiar with it, a regular expression can look like an alien language. Here we try to de-mystify this language a little bit. @@ -948,10 +929,10 @@ A regular expression is often applied to extract specific patterns from "unstruc There are four basic tools one can use to create a basic regular expression: -1) Character sets -2) Meta characters -3) Quantifiers -4) Groups +1) Character sets +2) Meta characters +3) Quantifiers +4) Groups **Character sets** @@ -989,13 +970,13 @@ Meta character | Represents Typically you do not want to search for a match on only one character. Quantifiers allow you to designate the length of letters/numbers to allow for the match. -Quantifiers are numbers written within curly brackets `{ }` *after* the character they are quantifying, for example, +Quantifiers are numbers written within curly brackets `{ }` *after* the character they are quantifying, for example: -* `"A{2}"` will return instances of **two** capital A letters. -* `"A{2,4}"` will return instances of **between two and four** capital A letters *(do not put spaces!)*. -* `"A{2,}"` will return instances of **two or more** capital A letters. -* `"A+"` will return instances of **one or more** capital A letters (group extended until a different character is encountered). -* Precede with an `*` asterisk to return **zero or more** matches (useful if you are not sure the pattern is present) +* `"A{2}"` will return instances of **two** capital A letters +* `"A{2,4}"` will return instances of **between two and four** capital A letters *(do not put spaces!)* +* `"A{2,}"` will return instances of **two or more** capital A letters +* `"A+"` will return instances of **one or more** capital A letters (group extended until a different character is encountered) +* Precede with an `*` asterisk to return **zero or more** matches (useful if you are not sure the pattern is present) Using the `+` plus symbol as a quantifier, the match will occur until a different character is encountered. For example, this expression will return all *words* (alpha characters: `"[A-Za-z]+"` @@ -1081,8 +1062,11 @@ You can view a useful list of regex expressions and tips on page 2 of [this chea Also see this [tutorial](https://towardsdatascience.com/a-gentle-introduction-to-regular-expressions-with-r-df5e897ca432). +Additionally, the package [**RVerbalExpressions**](https://rverbalexpressions.netlify.app/) can offer an easy way to construct regular expressions. - +```{r, eval = F} +devtools::install_github("VerbalExpressions/RVerbalExpressions") +``` ## Resources { } diff --git a/new_pages/cleaning.html b/new_pages/cleaning.html new file mode 100644 index 00000000..bbc38d8b --- /dev/null +++ b/new_pages/cleaning.html @@ -0,0 +1,3986 @@ + + + + + + + + + +8  Cleaning data and core functions – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    8  Cleaning data and core functions

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    This page demonstrates common steps used in the process of “cleaning” a dataset, and also explains the use of many essential R data management functions.

    +

    To demonstrate data cleaning, this page begins by importing a raw case linelist dataset, and proceeds step-by-step through the cleaning process. In the R code, this manifests as a “pipe” chain, which references the “pipe” operator %>% that passes a dataset from one operation to the next.

    +
    +

    Core functions

    +

    This handbook emphasizes use of the functions from the tidyverse family of R packages. The essential R functions demonstrated in this page are listed below.

    +

    Many of these functions belong to the dplyr R package, which provides “verb” functions to solve data manipulation challenges (the name is a reference to a “data frame-plier. dplyr is part of the tidyverse family of R packages (which also includes ggplot2, tidyr, stringr, tibble, purrr, magrittr, and forcats among others).

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FunctionUtilityPackage
    %>%“pipe” (pass) data from one function to the nextmagrittr
    mutate()create, transform, and re-define columnsdplyr
    select()keep, remove, select, or re-name columnsdplyr
    rename()rename columnsdplyr
    clean_names()standardize the syntax of column namesjanitor
    as.character(), as.numeric(), as.Date(), etc.convert the class of a columnbase R
    across()transform multiple columns at one timedplyr
    tidyselect functionsuse logic to select columnstidyselect
    filter()keep certain rowsdplyr
    distinct()de-duplicate rowsdplyr
    rowwise()operations by/within each rowdplyr
    add_row()add rows manuallytibble
    arrange()sort rowsdplyr
    recode()re-code values in a columndplyr
    case_when()re-code values in a column using more complex logical criteriadplyr
    replace_na(), na_if(), coalesce()special functions for re-codingtidyr
    age_categories() and cut()create categorical groups from a numeric columnepikit and base R
    match_df()re-code/clean values using a data dictionarymatchmaker
    which()apply logical criteria; return indicesbase R
    +

    If you want to see how these functions compare to Stata or SAS commands, see the page on Transition to R.

    +

    You may encounter an alternative data management framework from the data.table R package with operators like := and frequent use of brackets [ ]. This approach and syntax is briefly explained in the Data Table page.

    +
    +
    +

    Nomenclature

    +

    In this handbook, we generally reference “columns” and “rows” instead of “variables” and “observations”. As explained in this primer on “tidy data”, most epidemiological statistical datasets consist structurally of rows, columns, and values.

    +

    Variables contain the values that measure the same underlying attribute (like age group, outcome, or date of onset). Observations contain all values measured on the same unit (e.g. a person, site, or lab sample). So these aspects can be more difficult to tangibly define.

    +

    In “tidy” datasets, each column is a variable, each row is an observation, and each cell is a single value. However some datasets you encounter will not fit this mold - a “wide” format dataset may have a variable split across several columns (see an example in the Pivoting data page). Likewise, observations could be split across several rows.

    +

    Most of this handbook is about managing and transforming data, so referring to the concrete data structures of rows and columns is more relevant than the more abstract observations and variables. Exceptions occur primarily in pages on data analysis, where you will see more references to variables and observations.

    + + + +
    +
    +

    8.1 Cleaning pipeline

    +

    This page proceeds through typical cleaning steps, adding them sequentially to a cleaning pipe chain.

    +

    In epidemiological analysis and data processing, cleaning steps are often performed sequentially, linked together. In R, this often manifests as a cleaning “pipeline”, where the raw dataset is passed or “piped” from one cleaning step to another.

    +

    Such chains utilize dplyr “verb” functions and the magrittr pipe operator %>%. This pipe begins with the “raw” data (“linelist_raw.xlsx”) and ends with a “clean” R data frame (linelist) that can be used, saved, exported, etc.

    +

    In a cleaning pipeline the order of the steps is important. Cleaning steps might include:

    +
      +
    • Importing of data
    • +
    • Column names cleaned or changed
    • +
    • De-duplication
    • +
    • Column creation and transformation (e.g. re-coding or standardising values)
    • +
    • Rows filtered or added
    • +
    + + + +
    +
    +

    8.2 Load packages

    +

    This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.

    +
    +
    pacman::p_load(
    +  rio,        # importing data  
    +  here,       # relative file pathways  
    +  janitor,    # data cleaning and tables
    +  lubridate,  # working with dates
    +  matchmaker, # dictionary-based cleaning
    +  epikit,     # age_categories() function
    +  tidyverse   # data management and visualization
    +)
    +
    + + + +
    +
    +

    8.3 Import data

    +
    +

    Import

    +

    Here we import the “raw” case linelist Excel file using the import() function from the package rio. The rio package flexibly handles many types of files (e.g. .xlsx, .csv, .tsv, .rds. See the page on Import and export for more information and tips on unusual situations (e.g. skipping rows, setting missing values, importing Google sheets, etc).

    +

    If you want to follow along, click to download the “raw” linelist (as .xlsx file).

    +

    If your dataset is large and takes a long time to import, it can be useful to have the import command be separate from the pipe chain and the “raw” saved as a distinct file. This also allows easy comparison between the original and cleaned versions.

    +

    Below we import the raw Excel file and save it as the data frame linelist_raw. We assume the file is located in your working directory or R project root, and so no sub-folders are specified in the file path.

    +
    +
    linelist_raw <- import("linelist_raw.xlsx")
    +
    +

    You can view the first 50 rows of the the data frame below. Note: the base R function head(n) allow you to view just the first n rows in the R console.

    +
    +
    +
    + +
    +
    +
    +
    +

    Review

    +

    You can use the function skim() from the package skimr to get an overview of the entire dataframe (see page on Descriptive tables for more info). Columns are summarised by class/type such as character, numeric. Note: “POSIXct” is a type of raw date class (see Working with dates).

    +
    +
    skimr::skim(linelist_raw)
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Data summary
    Namelinelist_raw
    Number of rows6611
    Number of columns28
    _______________________
    Column type frequency:
    character17
    numeric8
    POSIXct3
    ________________________
    Group variablesNone
    +

    Variable type: character

    + ++++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    skim_variablen_missingcomplete_rateminmaxemptyn_uniquewhitespace
    case_id1370.9866058880
    date onset2930.96101005800
    outcome15000.7757020
    gender3240.9511020
    hospital15120.775360130
    infector23230.6566026970
    source23230.6557020
    age1070.98120750
    age_unit71.0056020
    fever2580.9623020
    chills2580.9623020
    cough2580.9623020
    aches2580.9623020
    vomit2580.9623020
    time_admission8440.8755010910
    merged_header01.0011010
    …2801.0011010
    +

    Variable type: numeric

    + ++++++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    skim_variablen_missingcomplete_ratemeansdp0p25p50p75p100
    generation71.0016.605.710.0013.0016.0020.0037.00
    lon71.00-13.230.02-13.27-13.25-13.23-13.22-13.21
    lat71.008.470.018.458.468.478.488.49
    row_num01.003240.911857.831.001647.503241.004836.506481.00
    wt_kg71.0052.6918.59-11.0041.0054.0066.00111.00
    ht_cm71.00125.2549.574.0091.00130.00159.00295.00
    ct_blood71.0021.261.6716.0020.0022.0022.0026.00
    temp1580.9838.600.9535.2038.3038.8039.2040.80
    +

    Variable type: POSIXct

    + +++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    skim_variablen_missingcomplete_rateminmaxmediann_unique
    infection date23220.652012-04-092015-04-272014-10-04538
    hosp date71.002012-04-202015-04-302014-10-15570
    date_of_outcome10680.842012-05-142015-06-042014-10-26575
    +
    +
    + + + +
    +
    +
    +

    8.4 Column names

    +

    In R, column names are the “header” or “top” value of a column. They are used to refer to columns in the code, and serve as a default label in figures.

    +

    Other statistical software such as SAS and STATA use “labels” that co-exist as longer printed versions of the shorter column names. While R does offer the possibility of adding column labels to the data, this is not emphasized in most practice. To make column names “printer-friendly” for figures, one typically adjusts their display within the plotting commands that create the outputs (e.g. axis or legend titles of a plot, or column headers in a printed table - see the scales section of the ggplot tips page and Tables for presentation pages). If you want to assign column labels in the data, read more online here and here.

    +

    As R column names are used very often, so they must have “clean” syntax. We suggest the following:

    +
      +
    • Short names
    • +
    • No spaces (replace with underscores _ )
    • +
    • No unusual characters (&, #, <, >, …)
    • +
    • Similar style nomenclature (e.g. all date columns named like date_onset, date_report, date_death…)
    • +
    +

    The columns names of linelist_raw are printed below using names() from base R. We can see that initially:

    +
      +
    • Some names contain spaces (e.g. infection date)
    • +
    • Different naming patterns are used for dates (date onset vs. infection date)
    • +
    • There must have been a merged header across the two last columns in the .xlsx. We know this because the name of two merged columns (“merged_header”) was assigned by R to the first column, and the second column was assigned a placeholder name “…28” (as it was then empty and is the 28th column)
    • +
    +
    +
    names(linelist_raw)
    +
    +
     [1] "case_id"         "generation"      "infection date"  "date onset"     
    + [5] "hosp date"       "date_of_outcome" "outcome"         "gender"         
    + [9] "hospital"        "lon"             "lat"             "infector"       
    +[13] "source"          "age"             "age_unit"        "row_num"        
    +[17] "wt_kg"           "ht_cm"           "ct_blood"        "fever"          
    +[21] "chills"          "cough"           "aches"           "vomit"          
    +[25] "temp"            "time_admission"  "merged_header"   "...28"          
    +
    +
    +

    NOTE: To reference a column name that includes spaces, surround the name with back-ticks, for example: linelist$`infection date`. note that on your keyboard, the back-tick (`) is different from the single quotation mark (’).

    +
    +

    Automatic cleaning

    +

    The function clean_names() from the package janitor standardizes column names and makes them unique by doing the following:

    +
      +
    • Converts all names to consist of only underscores, numbers, and letters
    • +
    • Accented characters are transliterated to ASCII (e.g. german o with umlaut becomes “o”, spanish “enye” becomes “n”)
    • +
    • Capitalization preference for the new column names can be specified using the case = argument (“snake” is default, alternatives include “sentence”, “title”, “small_camel”…)
    • +
    • You can specify specific name replacements by providing a vector to the replace = argument (e.g. replace = c(onset = "date_of_onset"))
    • +
    • Here is an online vignette
    • +
    +

    Below, the cleaning pipeline begins by using clean_names() on the raw linelist.

    +
    +
    # pipe the raw dataset through the function clean_names(), assign result as "linelist"  
    +linelist <- linelist_raw %>% 
    +  janitor::clean_names()
    +
    +# see the new column names
    +names(linelist)
    +
    +
     [1] "case_id"         "generation"      "infection_date"  "date_onset"     
    + [5] "hosp_date"       "date_of_outcome" "outcome"         "gender"         
    + [9] "hospital"        "lon"             "lat"             "infector"       
    +[13] "source"          "age"             "age_unit"        "row_num"        
    +[17] "wt_kg"           "ht_cm"           "ct_blood"        "fever"          
    +[21] "chills"          "cough"           "aches"           "vomit"          
    +[25] "temp"            "time_admission"  "merged_header"   "x28"            
    +
    +
    +

    NOTE: The last column name “…28” was changed to “x28”.

    +
    +
    +

    Manual name cleaning

    +

    Re-naming columns manually is often necessary, even after the standardization step above. Below, re-naming is performed using the rename() function from the dplyr package, as part of a pipe chain. rename() uses the style NEW = OLD, the new column name is given before the old column name.

    +

    Below, a re-naming command is added to the cleaning pipeline. Spaces have been added strategically to align code for easier reading.

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome)
    +
    +

    Now you can see that the columns names have been changed:

    +
    +
    +
     [1] "case_id"              "generation"           "date_infection"      
    + [4] "date_onset"           "date_hospitalisation" "date_outcome"        
    + [7] "outcome"              "gender"               "hospital"            
    +[10] "lon"                  "lat"                  "infector"            
    +[13] "source"               "age"                  "age_unit"            
    +[16] "row_num"              "wt_kg"                "ht_cm"               
    +[19] "ct_blood"             "fever"                "chills"              
    +[22] "cough"                "aches"                "vomit"               
    +[25] "temp"                 "time_admission"       "merged_header"       
    +[28] "x28"                 
    +
    +
    +
    +

    Rename by column position

    +

    You can also rename by column position, instead of column name, for example:

    +
    +
    rename(newNameForFirstColumn  = 1,
    +       newNameForSecondColumn = 2)
    +
    +
    +
    +

    Rename via select() and summarise()

    +

    As a shortcut, you can also rename columns within the dplyr select() and summarise() functions. select() is used to keep only certain columns (and is covered later in this page). summarise() is covered in the Grouping data and Descriptive tables pages. These functions also uses the format new_name = old_name. Here is an example:

    +
    +
    linelist_raw %>% 
    +  # rename and KEEP ONLY these columns
    +  select(# NEW name             # OLD name
    +         date_infection       = `infection date`,    
    +         date_hospitalisation = `hosp date`)
    +
    +
    +
    +
    +

    Other challenges

    +
    +

    Empty Excel column names

    +

    R cannot have dataset columns that do not have column names (headers). So, if you import an Excel dataset with data but no column headers, R will fill-in the headers with names like “…1” or “…2”. The number represents the column number (e.g. if the 4th column in the dataset has no header, then R will name it “…4”).

    +

    You can clean these names manually by referencing their position number (see example above), or their assigned name (linelist_raw$...1).

    +
    +
    +

    Merged Excel column names and cells

    +

    Merged cells in an Excel file are a common occurrence when receiving data. As explained in Transition to R, merged cells can be nice for human reading of data, but are not “tidy data” and cause many problems for machine reading of data. R cannot accommodate merged cells.

    +

    Remind people doing data entry that human-readable data is not the same as machine-readable data. Strive to train users about the principles of tidy data. If at all possible, try to change procedures so that data arrive in a tidy format without merged cells.

    +
      +
    • Each variable must have its own column
    • +
    • Each observation must have its own row
    • +
    • Each value must have its own cell
    • +
    +

    When using rio’s import() function, the value in a merged cell will be assigned to the first cell and subsequent cells will be empty.

    +

    One solution to deal with merged cells is to import the data with the function readWorkbook() from the package openxlsx. Set the argument fillMergedCells = TRUE. This gives the value in a merged cell to all cells within the merge range.

    +
    +
    linelist_raw <- openxlsx::readWorkbook("linelist_raw.xlsx", fillMergedCells = TRUE)
    +
    +

    DANGER: If column names are merged with readWorkbook(), you will end up with duplicate column names, which you will need to fix manually - R does not work well with duplicate column names! You can re-name them by referencing their position (e.g. column 5), as explained in the section on manual column name cleaning.

    + + + +
    +
    +
    +
    +

    8.5 Select or re-order columns

    +

    Use select() from dplyr to select the columns you want to retain, and to specify their order in the data frame.

    +

    CAUTION: In the examples below, the linelist data frame is modified with select() and displayed, but not saved. This is for demonstration purposes. The modified column names are printed by piping the data frame to names().

    +

    Here are ALL the column names in the linelist at this point in the cleaning pipe chain:

    +
    +
    names(linelist)
    +
    +
     [1] "case_id"              "generation"           "date_infection"      
    + [4] "date_onset"           "date_hospitalisation" "date_outcome"        
    + [7] "outcome"              "gender"               "hospital"            
    +[10] "lon"                  "lat"                  "infector"            
    +[13] "source"               "age"                  "age_unit"            
    +[16] "row_num"              "wt_kg"                "ht_cm"               
    +[19] "ct_blood"             "fever"                "chills"              
    +[22] "cough"                "aches"                "vomit"               
    +[25] "temp"                 "time_admission"       "merged_header"       
    +[28] "x28"                 
    +
    +
    +
    +

    Keep columns

    +

    Select only the columns you want to remain

    +

    Put their names in the select() command, with no quotation marks. They will appear in the data frame in the order you provide. Note that if you include a column that does not exist, R will return an error (see use of any_of() below if you want no error in this situation).

    +
    +
    # linelist dataset is piped through select() command, and names() prints just the column names
    +linelist %>% 
    +  select(case_id, date_onset, date_hospitalisation, fever) %>% 
    +  names()  # display the column names
    +
    +
    [1] "case_id"              "date_onset"           "date_hospitalisation"
    +[4] "fever"               
    +
    +
    +
    +
    +

    “tidyselect” helper functions

    +

    These helper functions exist to make it easy to specify columns to keep, discard, or transform. They are from the package tidyselect, which is included in tidyverse and underlies how columns are selected in dplyr functions.

    +

    For example, if you want to re-order the columns, everything() is a useful function to signify “all other columns not yet mentioned”. The command below moves columns date_onset and date_hospitalisation to the beginning (left) of the dataset, but keeps all the other columns afterward. Note that everything() is written with empty parentheses:

    +
    +
    # move date_onset and date_hospitalisation to beginning
    +linelist %>% 
    +  select(date_onset, date_hospitalisation, everything()) %>% 
    +  names()
    +
    +
     [1] "date_onset"           "date_hospitalisation" "case_id"             
    + [4] "generation"           "date_infection"       "date_outcome"        
    + [7] "outcome"              "gender"               "hospital"            
    +[10] "lon"                  "lat"                  "infector"            
    +[13] "source"               "age"                  "age_unit"            
    +[16] "row_num"              "wt_kg"                "ht_cm"               
    +[19] "ct_blood"             "fever"                "chills"              
    +[22] "cough"                "aches"                "vomit"               
    +[25] "temp"                 "time_admission"       "merged_header"       
    +[28] "x28"                 
    +
    +
    +

    Here are other “tidyselect” helper functions that also work within dplyr functions like select(), across(), and summarise():

    +
      +
    • everything() - all other columns not mentioned
      +
    • +
    • last_col() - the last column
    • +
    • where() - applies a function to all columns and selects those which are TRUE
      +
    • +
    • contains() - columns containing a character string +
        +
      • example: select(contains("time"))
        +
      • +
    • +
    • starts_with() - matches to a specified prefix +
        +
      • example: select(starts_with("date_"))
        +
      • +
    • +
    • ends_with() - matches to a specified suffix +
        +
      • example: select(ends_with("_post"))
        +
      • +
    • +
    • matches() - to apply a regular expression (regex) +
        +
      • example: select(matches("[pt]al"))
      • +
    • +
    • num_range() - a numerical range like x01, x02, x03
      +
    • +
    • any_of() - matches IF column exists but returns no error if it is not found +
        +
      • example: select(any_of(date_onset, date_death, cardiac_arrest))
      • +
    • +
    +

    In addition, use normal operators such as c() to list several columns, : for consecutive columns, ! for opposite, & for AND, and | for OR.

    +

    Use where() to specify logical criteria for columns. If providing a function inside where(), do not include the function’s empty parentheses. The command below selects columns that are class Numeric.

    +
    +
    # select columns that are class Numeric
    +linelist %>% 
    +  select(where(is.numeric)) %>% 
    +  names()
    +
    +
    [1] "generation" "lon"        "lat"        "row_num"    "wt_kg"     
    +[6] "ht_cm"      "ct_blood"   "temp"      
    +
    +
    +

    Use contains() to select only columns in which the column name contains a specified character string. ends_with() and starts_with() provide more nuance.

    +
    +
    # select columns containing certain characters
    +linelist %>% 
    +  select(contains("date")) %>% 
    +  names()
    +
    +
    [1] "date_infection"       "date_onset"           "date_hospitalisation"
    +[4] "date_outcome"        
    +
    +
    +

    The function matches() works similarly to contains() but can be provided a regular expression (see page on Characters and strings), such as multiple strings separated by OR bars within the parentheses:

    +
    +
    # searched for multiple character matches
    +linelist %>% 
    +  select(matches("onset|hosp|fev")) %>%   # note the OR symbol "|"
    +  names()
    +
    +
    [1] "date_onset"           "date_hospitalisation" "hospital"            
    +[4] "fever"               
    +
    +
    +

    CAUTION: If a column name that you specifically provide does not exist in the data, it can return an error and stop your code. Consider using any_of() to cite columns that may or may not exist, especially useful in negative (remove) selections.

    +

    Only one of these columns exists, but no error is produced and the code continues without stopping your cleaning chain.

    +
    +
    linelist %>% 
    +  select(any_of(c("date_onset", "village_origin", "village_detection", "village_residence", "village_travel"))) %>% 
    +  names()
    +
    +
    [1] "date_onset"
    +
    +
    +
    +
    +

    Remove columns

    +

    Indicate which columns to remove by placing a minus symbol “-” in front of the column name (e.g. select(-outcome)), or a vector of column names (as below). All other columns will be retained.

    +
    +
    linelist %>% 
    +  select(-c(date_onset, fever:vomit)) %>% # remove date_onset and all columns from fever to vomit
    +  names()
    +
    +
     [1] "case_id"              "generation"           "date_infection"      
    + [4] "date_hospitalisation" "date_outcome"         "outcome"             
    + [7] "gender"               "hospital"             "lon"                 
    +[10] "lat"                  "infector"             "source"              
    +[13] "age"                  "age_unit"             "row_num"             
    +[16] "wt_kg"                "ht_cm"                "ct_blood"            
    +[19] "temp"                 "time_admission"       "merged_header"       
    +[22] "x28"                 
    +
    +
    +

    You can also remove a column using base R syntax, by defining it as NULL. For example:

    +
    +
    linelist$date_onset <- NULL   # deletes column with base R syntax 
    +
    +
    +
    +

    Standalone

    +

    select() can also be used as an independent command (not in a pipe chain). In this case, the first argument is the original dataframe to be operated upon.

    +
    +
    # Create a new linelist with id and age-related columns
    +linelist_age <- select(linelist, case_id, contains("age"))
    +
    +# display the column names
    +names(linelist_age)
    +
    +
    [1] "case_id"  "age"      "age_unit"
    +
    +
    +
    +

    Add to the pipe chain

    +

    In the linelist_raw, there are a few columns we do not need: row_num, merged_header, and x28. We remove them with a select() command in the cleaning pipe chain:

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +
    +# begin cleaning pipe chain
    +###########################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome) %>% 
    +    
    +    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    +    #####################################################
    +
    +    # remove column
    +    select(-c(row_num, merged_header, x28))
    +
    + + + +
    +
    +
    +
    +

    8.6 Deduplication

    +

    See the handbook page on De-duplication for extensive options on how to de-duplicate data. Only a very simple row de-duplication example is presented here.

    +

    The package dplyr offers the distinct() function. This function examines every row and reduce the data frame to only the unique rows. That is, it removes rows that are 100% duplicates.

    +

    When evaluating duplicate rows, it takes into account a range of columns - by default it considers all columns. As shown in the de-duplication page, you can adjust this column range so that the uniqueness of rows is only evaluated in regards to certain columns.

    +

    In this simple example, we just add the empty command distinct() to the pipe chain. This ensures there are no rows that are 100% duplicates of other rows (evaluated across all columns).

    +

    We begin with nrow(linelist) rows in linelist.

    +
    +
    linelist <- linelist %>% 
    +  distinct()
    +
    +

    After de-duplication there are nrow(linelist) rows. Any removed rows would have been 100% duplicates of other rows.

    +

    Below, the distinct() command is added to the cleaning pipe chain:

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +
    +# begin cleaning pipe chain
    +###########################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome) %>% 
    +    
    +    # remove column
    +    select(-c(row_num, merged_header, x28)) %>% 
    +  
    +    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    +    #####################################################
    +    
    +    # de-duplicate
    +    distinct()
    +
    + + + +
    +
    +

    8.7 Column creation and transformation

    +

    We recommend using the dplyr function mutate() to add a new column, or to modify an existing one.

    +

    Below is an example of creating a new column with mutate(). The syntax is: mutate(new_column_name = value or transformation).

    +

    In Stata, this is similar to the command generate, but R’s mutate() can also be used to modify an existing column.

    +
    +

    New columns

    +

    The most basic mutate() command to create a new column might look like this. It creates a new column new_col where the value in every row is 10.

    +
    +
    linelist <- linelist %>% 
    +  mutate(new_col = 10)
    +
    +

    You can also reference values in other columns, to perform calculations. Below, a new column bmi is created to hold the Body Mass Index (BMI) for each case - as calculated using the formula BMI = kg/m^2, using column ht_cm and column wt_kg.

    +
    +
    linelist <- linelist %>% 
    +  mutate(bmi = wt_kg / (ht_cm/100)^2)
    +
    +

    If creating multiple new columns, separate each with a comma and new line. Below are examples of new columns, including ones that consist of values from other columns combined using str_glue() from the stringr package (see page on Characters and strings.

    +
    +
    new_col_demo <- linelist %>%                       
    +  mutate(
    +    new_var_dup    = case_id,             # new column = duplicate/copy another existing column
    +    new_var_static = 7,                   # new column = all values the same
    +    new_var_static = new_var_static + 5,  # you can overwrite a column, and it can be a calculation using other variables
    +    new_var_paste  = stringr::str_glue("{hospital} on ({date_hospitalisation})") # new column = pasting together values from other columns
    +    ) %>% 
    +  select(case_id, hospital, date_hospitalisation, contains("new"))        # show only new columns, for demonstration purposes
    +
    +

    Review the new columns. For demonstration purposes, only the new columns and the columns used to create them are shown:

    +
    +
    +
    + +
    +
    +

    TIP: A variation on mutate() is the function transmute(). This function adds a new column just like mutate(), but also drops/removes all other columns that you do not mention within its parentheses.

    +
    +
    # HIDDEN FROM READER
    +# removes new demo columns created above
    +# linelist <- linelist %>% 
    +#   select(-contains("new_var"))
    +
    +
    +
    +

    Convert column class

    +

    Columns containing values that are dates, numbers, or logical values (TRUE/FALSE) will only behave as expected if they are correctly classified. There is a difference between “2” of class character and 2 of class numeric!

    +

    There are ways to set column class during the import commands, but this is often cumbersome. See the R Basics section on object classes to learn more about converting the class of objects and columns.

    +

    First, let’s run some checks on important columns to see if they are the correct class. We also saw this in the beginning when we ran skim().

    +

    Currently, the class of the age column is character. To perform quantitative analyses, we need these numbers to be recognized as numeric!

    +
    +
    class(linelist$age)
    +
    +
    [1] "character"
    +
    +
    +

    The class of the date_onset column is also character! To perform analyses, these dates must be recognized as dates!

    +
    +
    class(linelist$date_onset)
    +
    +
    [1] "character"
    +
    +
    +

    To resolve this, use the ability of mutate() to re-define a column with a transformation. We define the column as itself, but converted to a different class. Here is a basic example, converting or ensuring that the column age is class Numeric:

    +
    +
    linelist <- linelist %>% 
    +  mutate(age = as.numeric(age))
    +
    +

    In a similar way, you can use as.character() and as.logical(). To convert to class Factor, you can use factor() from base R or as_factor() from forcats. Read more about this in the Factors page.

    +

    You must be careful when converting to class Date. Several methods are explained on the page Working with dates. Typically, the raw date values must all be in the same format for conversion to work correctly (e.g “MM/DD/YYYY”, or “DD MM YYYY”). After converting to class Date, check your data to confirm that each value was converted correctly.

    +
    +
    +

    Grouped data

    +

    If your data frame is already grouped (see page on Grouping data), mutate() may behave differently than if the data frame is not grouped. Any summarizing functions, like mean(), median(), max(), etc. will calculate by group, not by all the rows.

    +
    +
    # age normalized to mean of ALL rows
    +linelist %>% 
    +  mutate(age_norm = age / mean(age, na.rm=T))
    +
    +# age normalized to mean of hospital group
    +linelist %>% 
    +  group_by(hospital) %>% 
    +  mutate(age_norm = age / mean(age, na.rm=T))
    +
    +

    Read more about using mutate () on grouped dataframes in this tidyverse mutate documentation.

    +
    +
    +

    Transform multiple columns

    +

    Often to write concise code you want to apply the same transformation to multiple columns at once. A transformation can be applied to multiple columns at once using the across() function from the package dplyr (also contained within tidyverse package). across() can be used with any dplyr function, but is commonly used within select(), mutate(), filter(), or summarise(). See how it is applied to summarise() in the page on Descriptive tables.

    +

    Specify the columns to the argument .cols = and the function(s) to apply to .fns =. Any additional arguments to provide to the .fns function can be included after a comma, still within across().

    +
    +

    across() column selection

    +

    Specify the columns to the argument .cols =. You can name them individually, or use “tidyselect” helper functions. Specify the function to .fns =. Note that using the function mode demonstrated below, the function is written without its parentheses ( ).

    +

    Here the transformation as.character() is applied to specific columns named within across().

    +
    +
    linelist <- linelist %>% 
    +  mutate(across(.cols = c(temp, ht_cm, wt_kg), .fns = as.character))
    +
    +

    The “tidyselect” helper functions are available to assist you in specifying columns. They are detailed above in the section on Selecting and re-ordering columns, and they include: everything(), last_col(), where(), starts_with(), ends_with(), contains(), matches(), num_range() and any_of().

    +

    Here is an example of how one would change all columns to character class:

    +
    +
    #to change all columns to character class
    +linelist <- linelist %>% 
    +  mutate(across(.cols = everything(), .fns = as.character))
    +
    +

    Convert to character all columns where the name contains the string “date” (note the placement of commas and parentheses):

    +
    +
    #to change all columns to character class
    +linelist <- linelist %>% 
    +  mutate(across(.cols = contains("date"), .fns = as.character))
    +
    +

    Below, an example of mutating the columns that are currently class POSIXct (a raw datetime class that shows timestamps) - in other words, where the function is.POSIXct() evaluates to TRUE. Then we want to apply the function as.Date() to these columns to convert them to a normal class Date.

    +
    +
    linelist <- linelist %>% 
    +  mutate(across(.cols = where(is.POSIXct), .fns = as.Date))
    +
    +
      +
    • Note that within across() we also use the function where() as is.POSIXct is evaluating to either TRUE or FALSE.
      +
    • +
    • Note that is.POSIXct() is from the package lubridate. Other similar “is” functions like is.character(), is.numeric(), and is.logical() are from base R.
    • +
    +
    +
    +

    across() functions

    +

    You can read the documentation with ?across for details on how to provide functions to across(). A few summary points: there are several ways to specify the function(s) to perform on a column and you can even define your own functions:

    +
      +
    • You can provide the function name alone (e.g. mean or as.character)
    • +
    • You can provide the function in purrr-style (e.g. ~ mean(.x, na.rm = TRUE)) (see this page)
    • +
    • You can specify multiple functions by providing a list (e.g. list(mean = mean, n_miss = ~ sum(is.na(.x))) +
        +
      • If you provide multiple functions, multiple transformed columns will be returned per input column, with unique names in the format col_fn. You can adjust how the new columns are named with the .names = argument using glue syntax (see page on Characters and strings) where {.col} and {.fn} are shorthand for the input column and function
      • +
    • +
    +

    Here are a few online resources on using across(): creator Hadley Wickham’s thoughts/rationale

    +
    +
    +
    +

    coalesce()

    +

    This dplyr function finds the first non-missing value at each position. It “fills-in” missing values with the first available value in an order you specify.

    +

    Here is an example outside the context of a data frame: Let us say you have two vectors, one containing the patient’s village of detection and another containing the patient’s village of residence. You can use coalesce to pick the first non-missing value for each index:

    +
    +
    village_detection <- c("a", "b", NA,  NA)
    +village_residence <- c("a", "c", "a", "d")
    +
    +village <- coalesce(village_detection, village_residence)
    +village    # print
    +
    +
    [1] "a" "b" "a" "d"
    +
    +
    +

    This works the same if you provide data frame columns: for each row, the function will assign the new column value with the first non-missing value in the columns you provided (in order provided).

    +
    +
    linelist <- linelist %>% 
    +  mutate(village = coalesce(village_detection, village_residence))
    +
    +

    This is an example of a “row-wise” operation. For more complicated row-wise calculations, see the section below on Row-wise calculations.

    +
    +
    +

    Cumulative math

    +

    If you want a column to reflect the cumulative sum/mean/min/max etc as assessed down the rows of a dataframe to that point, use the following functions:

    +

    cumsum() returns the cumulative sum, as shown below:

    +
    +
    sum(c(2,4,15,10))     # returns only one number
    +
    +
    [1] 31
    +
    +
    cumsum(c(2,4,15,10))  # returns the cumulative sum at each step
    +
    +
    [1]  2  6 21 31
    +
    +
    +

    This can be used in a dataframe when making a new column. For example, to calculate the cumulative number of cases per day in an outbreak, consider code like this:

    +
    +
    cumulative_case_counts <- linelist %>%  # begin with case linelist
    +  count(date_onset) %>%                 # count of rows per day, as column 'n'   
    +  mutate(cumulative_cases = cumsum(n))  # new column, of the cumulative sum at each row
    +
    +

    Below are the first 10 rows:

    +
    +
    head(cumulative_case_counts, 10)
    +
    +
       date_onset n cumulative_cases
    +1  2012-04-15 1                1
    +2  2012-05-05 1                2
    +3  2012-05-08 1                3
    +4  2012-05-31 1                4
    +5  2012-06-02 1                5
    +6  2012-06-07 1                6
    +7  2012-06-14 1                7
    +8  2012-06-21 1                8
    +9  2012-06-24 1                9
    +10 2012-06-25 1               10
    +
    +
    +

    See the page on Epidemic curves for how to plot cumulative incidence with the epicurve.

    +

    See also:
    +cumsum(), cummean(), cummin(), cummax(), cumany(), cumall()

    +
    +
    +

    Using base R

    +

    To define a new column (or re-define a column) using base R, write the name of data frame, connected with $, to the new column (or the column to be modified). Use the assignment operator <- to define the new value(s). Remember that when using base R you must specify the data frame name before the column name every time (e.g. dataframe$column). Here is an example of creating the bmi column using base R:

    +
    +
    linelist$bmi = linelist$wt_kg / (linelist$ht_cm / 100) ^ 2)
    +
    +
    +
    +

    Add to pipe chain

    +

    Below, a new column is added to the pipe chain and some classes are converted.

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +
    +# begin cleaning pipe chain
    +###########################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome) %>% 
    +    
    +    # remove column
    +    select(-c(row_num, merged_header, x28)) %>% 
    +  
    +    # de-duplicate
    +    distinct() %>% 
    +  
    +    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    +    ###################################################
    +    # add new column
    +    mutate(bmi = wt_kg / (ht_cm/100)^2) %>% 
    +  
    +    # convert class of columns
    +    mutate(across(contains("date"), as.Date), 
    +           generation = as.numeric(generation),
    +           age        = as.numeric(age)) 
    +
    +
    +
    +
    +

    8.8 Re-code values

    +

    Here are a few scenarios where you need to re-code (change) values:

    +
      +
    • to edit one specific value (e.g. one date with an incorrect year or format)
    • +
    • to reconcile values not spelled the same
    • +
    • to create a new column of categorical values
    • +
    • to create a new column of numeric categories (e.g. age categories)
    • +
    +
    +

    Specific values

    +

    To change values manually you can use the recode() function within the mutate() function.

    +

    Imagine there is a nonsensical date in the data (e.g. “2014-14-15”): you could fix the date manually in the raw source data, or, you could write the change into the cleaning pipeline via mutate() and recode(). The latter is more transparent and reproducible to anyone else seeking to understand or repeat your analysis.

    +
    +
    # fix incorrect values                   # old value       # new value
    +linelist <- linelist %>% 
    +  mutate(date_onset = recode(date_onset, "2014-14-15" = "2014-04-15"))
    +
    +

    The mutate() line above can be read as: “mutate the column date_onset to equal the column date_onset re-coded so that OLD VALUE is changed to NEW VALUE”. Note that this pattern (OLD = NEW) for recode() is the opposite of most R patterns (new = old). The R development community is working on revising this.

    +

    Here is another example re-coding multiple values within one column.

    +

    In linelist the values in the column “hospital” must be cleaned. There are several different spellings and many missing values.

    +
    +
    table(linelist$hospital, useNA = "always")  # print table of all unique values, including missing  
    +
    +
    
    +                     Central Hopital                     Central Hospital 
    +                                  11                                  457 
    +                          Hospital A                           Hospital B 
    +                                 290                                  289 
    +                    Military Hopital                    Military Hospital 
    +                                  32                                  798 
    +                    Mitylira Hopital                    Mitylira Hospital 
    +                                   1                                   79 
    +                               Other                         Port Hopital 
    +                                 907                                   48 
    +                       Port Hospital St. Mark's Maternity Hospital (SMMH) 
    +                                1756                                  417 
    +  St. Marks Maternity Hopital (SMMH)                                 <NA> 
    +                                  11                                 1512 
    +
    +
    +

    The recode() command below re-defines the column “hospital” as the current column “hospital”, but with the specified recode changes. Don’t forget commas after each!

    +
    +
    linelist <- linelist %>% 
    +  mutate(hospital = recode(hospital,
    +                     # for reference: OLD = NEW
    +                      "Mitylira Hopital"  = "Military Hospital",
    +                      "Mitylira Hospital" = "Military Hospital",
    +                      "Military Hopital"  = "Military Hospital",
    +                      "Port Hopital"      = "Port Hospital",
    +                      "Central Hopital"   = "Central Hospital",
    +                      "other"             = "Other",
    +                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
    +                      ))
    +
    +

    Now we see the spellings in the hospital column have been corrected and consolidated:

    +
    +
    table(linelist$hospital, useNA = "always")
    +
    +
    
    +                    Central Hospital                           Hospital A 
    +                                 468                                  290 
    +                          Hospital B                    Military Hospital 
    +                                 289                                  910 
    +                               Other                        Port Hospital 
    +                                 907                                 1804 
    +St. Mark's Maternity Hospital (SMMH)                                 <NA> 
    +                                 428                                 1512 
    +
    +
    +

    TIP: The number of spaces before and after an equals sign does not matter. Make your code easier to read by aligning the = for all or most rows. Also, consider adding a hashed comment row to clarify for future readers which side is OLD and which side is NEW.

    +

    TIP: Sometimes a blank character value exists in a dataset (not recognized as R’s value for missing - NA). You can reference this value with two quotation marks with no space inbetween (““).

    +
    +
    +

    By logic

    +

    Below we demonstrate how to re-code values in a column using logic and conditions:

    +
      +
    • Using replace(), ifelse() and if_else() for simple logic
    • +
    • Using case_when() for more complex logic
    • +
    +
    +
    +

    Simple logic

    +
    +

    replace()

    +

    To re-code with simple logical criteria, you can use replace() within mutate(). replace() is a function from base R. Use a logic condition to specify the rows to change . The general syntax is:

    +
    +
    mutate(col_to_change = replace(col_to_change, criteria for rows, new value))
    +
    +

    One common situation to use replace() is changing just one value in one row, using an unique row identifier. Below, the gender is changed to “Female” in the row where the column case_id is “2195”.

    +
    +
    # Example: change gender of one specific observation to "Female" 
    +linelist <- linelist %>% 
    +  mutate(gender = replace(gender, case_id == "2195", "Female"))
    +
    +

    The equivalent command using base R syntax and indexing brackets [ ] is below. It reads as “Change the value of the dataframe linelist‘s column gender (for the rows where linelist’s column case_id has the value ’2195’) to ‘Female’”.

    +
    +
    linelist$gender[linelist$case_id == "2195"] <- "Female"
    +
    +
    +
    +

    ifelse() and if_else()

    +

    Another tool for simple logic is ifelse() and its partner if_else(). However, in most cases for re-coding it is more clear to use case_when() (detailed below). These “if else” commands are simplified versions of an if and else programming statement. The general syntax is:
    +ifelse(condition, value to return if condition evaluates to TRUE, value to return if condition evaluates to FALSE)

    +

    Below, the column source_known is defined. Its value in a given row is set to “known” if the row’s value in column source is not missing. If the value in source is missing, then the value in source_known is set to “unknown”.

    +
    +
    linelist <- linelist %>% 
    +  mutate(source_known = ifelse(!is.na(source), "known", "unknown"))
    +
    +

    if_else() is a special version from dplyr that handles dates. Note that if the ‘true’ value is a date, the ‘false’ value must also qualify a date, hence using the special value NA_real_ instead of just NA.

    +
    +
    # Create a date of death column, which is NA if patient has not died.
    +linelist <- linelist %>% 
    +  mutate(date_death = if_else(outcome == "Death", date_outcome, NA_real_))
    +
    +

    Avoid stringing together many ifelse commands… use case_when() instead! case_when() is much easier to read and you’ll make fewer errors.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Outside of the context of a data frame, if you want to have an object used in your code switch its value, consider using switch() from base R.

    +
    +
    +
    +

    Complex logic

    +

    Use dplyr’s case_when() if you are re-coding into many new groups, or if you need to use complex logic statements to re-code values. This function evaluates every row in the data frame, assess whether the rows meets specified criteria, and assigns the correct new value.

    +

    case_when() commands consist of statements that have a Right-Hand Side (RHS) and a Left-Hand Side (LHS) separated by a “tilde” ~. The logic criteria are in the left side and the pursuant values are in the right side of each statement. Statements are separated by commas.

    +

    For example, here we utilize the columns age and age_unit to create a column age_years:

    +
    +
    linelist <- linelist %>% 
    +  mutate(age_years = case_when(
    +       age_unit == "years"  ~ age,       # if age unit is years
    +       age_unit == "months" ~ age/12,    # if age unit is months, divide age by 12
    +       is.na(age_unit)      ~ age))      # if age unit is missing, assume years
    +                                         # any other circumstance, assign NA (missing)
    +
    +

    As each row in the data is evaluated, the criteria are applied/evaluated in the order the case_when() statements are written, from top-to-bottom. If the top criteria evaluates to TRUE for a given row, the RHS value is assigned, and the remaining criteria are not even tested for that row in the data. Thus, it is best to write the most specific criteria first, and the most general last. A data row that does not meet any of the RHS criteria will be assigned NA.

    +

    Sometimes, you may with to write a final statement that assigns a value for all other scenarios not described by one of the previous lines. To do this, place TRUE on the left-side, which will capture any row that did not meet any of the previous criteria. The right-side of this statement could be assigned a value like “check me!” or missing.

    +

    Below is another example of case_when() used to create a new column with the patient classification, according to a case definition for confirmed and suspect cases:

    +
    +
    linelist <- linelist %>% 
    +     mutate(case_status = case_when(
    +          
    +          # if patient had lab test and it is positive,
    +          # then they are marked as a confirmed case 
    +          ct_blood < 20                   ~ "Confirmed",
    +          
    +          # given that a patient does not have a positive lab result,
    +          # if patient has a "source" (epidemiological link) AND has fever, 
    +          # then they are marked as a suspect case
    +          !is.na(source) & fever == "yes" ~ "Suspect",
    +          
    +          # any other patient not addressed above 
    +          # is marked for follow up
    +          TRUE                            ~ "To investigate"))
    +
    +

    DANGER: Values on the right-side must all be the same class - either numeric, character, date, logical, etc. To assign missing (NA), you may need to use special variations of NA such as NA_character_, NA_real_ (for numeric or POSIX), and as.Date(NA). Read more in Working with dates.

    +
    +
    +

    Missing values

    +

    Below are special functions for handling missing values in the context of data cleaning.

    +

    See the page on Missing data for more detailed tips on identifying and handling missing values. For example, the is.na() function which logically tests for missingness.

    +

    replace_na()

    +

    To change missing values (NA) to a specific value, such as “Missing”, use the dplyr function replace_na() within mutate(). Note that this is used in the same manner as recode above - the name of the variable must be repeated within replace_na().

    +
    +
    linelist <- linelist %>% 
    +  mutate(hospital = replace_na(hospital, "Missing"))
    +
    +

    fct_na_value_to_level()

    +

    This is a function from the forcats package. The forcats package handles columns of class Factor. Factors are R’s way to handle ordered values such as c("First", "Second", "Third") or to set the order that values (e.g. hospitals) appear in tables and plots. See the page on Factors.

    +

    If your data are class Factor and you try to convert NA to “Missing” by using replace_na(), you will get this error: invalid factor level, NA generated. You have tried to add “Missing” as a value, when it was not defined as a possible level of the factor, and it was rejected.

    +

    The easiest way to solve this is to use the forcats function fct_na_value_to_level() which converts a column to class factor, and converts NA values to the character “(Missing)”.

    +
    +
    linelist %>% 
    +  mutate(hospital = fct_na_value_to_level(hospital))
    +
    +

    A slower alternative would be to add the factor level using fct_expand() and then convert the missing values.

    +

    na_if()

    +

    To convert a specific value to NA, use dplyr’s na_if(). The command below performs the opposite operation of replace_na(). In the example below, any values of “Missing” in the column hospital are converted to NA.

    +
    +
    linelist <- linelist %>% 
    +  mutate(hospital = na_if(hospital, "Missing"))
    +
    +

    Note: na_if() cannot be used for logic criteria (e.g. “all values > 99”) - use replace() or case_when() for this:

    +
    +
    # Convert temperatures above 40 to NA 
    +linelist <- linelist %>% 
    +  mutate(temp = replace(temp, temp > 40, NA))
    +
    +# Convert onset dates earlier than 1 Jan 2000 to missing
    +linelist <- linelist %>% 
    +  mutate(date_onset = replace(date_onset, date_onset > as.Date("2000-01-01"), NA))
    +
    +
    +
    +

    Cleaning dictionary

    +

    Use the R package matchmaker and its function match_df() to clean a data frame with a cleaning dictionary.

    +
      +
    1. Create a cleaning dictionary with 3 columns: +
        +
      • A “from” column (the incorrect value)
      • +
      • A “to” column (the correct value)
      • +
      • A column specifying the column for the changes to be applied (or “.global” to apply to all columns)
      • +
    2. +
    +

    Note: .global dictionary entries will be overridden by column-specific dictionary entries.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
      +
    1. Import the dictionary file into R. This example can be downloaded via instructions on the Download handbook and data page.
    2. +
    +
    +
    cleaning_dict <- import("cleaning_dict.csv")
    +
    +
      +
    1. Pipe the raw linelist to match_df(), specifying to dictionary = the cleaning dictionary data frame. The from = argument should be the name of the dictionary column which contains the “old” values, the by = argument should be dictionary column which contains the corresponding “new” values, and the third column lists the column in which to make the change. Use .global in the by = column to apply a change across all columns. A fourth dictionary column order can be used to specify factor order of new values.
    2. +
    +

    Read more details in the package documentation by running ?match_df. Note this function can take a long time to run for a large dataset.

    +
    +
    linelist <- linelist %>%     # provide or pipe your dataset
    +     matchmaker::match_df(
    +          dictionary = cleaning_dict,  # name of your dictionary
    +          from = "from",               # column with values to be replaced (default is col 1)
    +          to = "to",                   # column with final values (default is col 2)
    +          by = "col"                   # column with column names (default is col 3)
    +  )
    +
    +

    Now scroll to the right to see how values have changed - particularly gender (lowercase to uppercase), and all the symptoms columns have been transformed from yes/no to 1/0.

    +
    +
    +
    + +
    +
    +

    Note that your column names in the cleaning dictionary must correspond to the names at this point in your cleaning script. See this online reference for the linelist package for more details.

    +
    +

    Add to pipe chain

    +

    Below, some new columns and column transformations are added to the pipe chain.

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +
    +# begin cleaning pipe chain
    +###########################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome) %>% 
    +    
    +    # remove column
    +    select(-c(row_num, merged_header, x28)) %>% 
    +  
    +    # de-duplicate
    +    distinct() %>% 
    +  
    +    # add column
    +    mutate(bmi = wt_kg / (ht_cm/100)^2) %>%     
    +
    +    # convert class of columns
    +    mutate(across(contains("date"), as.Date), 
    +           generation = as.numeric(generation),
    +           age        = as.numeric(age)) %>% 
    +    
    +    # add column: delay to hospitalisation
    +    mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% 
    +    
    +   # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    +   ###################################################
    +
    +    # clean values of hospital column
    +    mutate(hospital = recode(hospital,
    +                      # OLD = NEW
    +                      "Mitylira Hopital"  = "Military Hospital",
    +                      "Mitylira Hospital" = "Military Hospital",
    +                      "Military Hopital"  = "Military Hospital",
    +                      "Port Hopital"      = "Port Hospital",
    +                      "Central Hopital"   = "Central Hospital",
    +                      "other"             = "Other",
    +                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
    +                      )) %>% 
    +    
    +    mutate(hospital = replace_na(hospital, "Missing")) %>% 
    +
    +    # create age_years column (from age and age_unit)
    +    mutate(age_years = case_when(
    +          age_unit == "years" ~ age,
    +          age_unit == "months" ~ age/12,
    +          is.na(age_unit) ~ age,
    +          TRUE ~ NA_real_))
    +
    + + + +
    +
    +
    +
    +

    8.9 Numeric categories

    +

    Here we describe some special approaches for creating categories from numerical columns. Common examples include age categories, groups of lab values, etc. Here we will discuss:

    +
      +
    • age_categories(), from the epikit package
    • +
    • cut(), from base R
    • +
    • case_when()
    • +
    • quantile breaks with quantile() and ntile()
    • +
    +
    +

    Review distribution

    +

    For this example we will create an age_cat column using the age_years column.

    +
    +
    #check the class of the linelist variable age
    +class(linelist$age_years)
    +
    +
    [1] "numeric"
    +
    +
    +

    First, examine the distribution of your data, to make appropriate cut-points. See the page on ggplot basics.

    +
    +
    # examine the distribution
    +hist(linelist$age_years)
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    summary(linelist$age_years, na.rm=T)
    +
    +
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
    +   0.00    6.00   13.00   16.04   23.00   84.00     107 
    +
    +
    +

    CAUTION: Sometimes, numeric variables will import as class “character”. This occurs if there are non-numeric characters in some of the values, for example an entry of “2 months” for age, or (depending on your R locale settings) if a comma is used in the decimals place (e.g. “4,5” to mean four and one half years)..

    + +
    +
    +

    age_categories()

    +

    With the epikit package, you can use the age_categories() function to easily categorize and label numeric columns (note: this function can be applied to non-age numeric variables too). As a bonum, the output column is automatically an ordered factor.

    +

    Here are the required inputs:

    +
      +
    • A numeric vector (column)
      +
    • +
    • The breakers = argument - provide a numeric vector of break points for the new groups
    • +
    +

    First, the simplest example:

    +
    +
    # Simple example
    +################
    +pacman::p_load(epikit)                    # load package
    +
    +linelist <- linelist %>% 
    +  mutate(
    +    age_cat = age_categories(             # create new column
    +      age_years,                            # numeric column to make groups from
    +      breakers = c(0, 5, 10, 15, 20,        # break points
    +                   30, 40, 50, 60, 70)))
    +
    +# show table
    +table(linelist$age_cat, useNA = "always")
    +
    +
    
    +  0-4   5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-69   70+  <NA> 
    + 1227  1223  1048   827  1216   597   251    78    27     7   107 
    +
    +
    +

    The break values you specify are by default the lower bounds - that is, they are included in the “higher” group / the groups are “open” on the lower/left side. As shown below, you can add 1 to each break value to achieve groups that are open at the top/right.

    +
    +
    # Include upper ends for the same categories
    +############################################
    +linelist <- linelist %>% 
    +  mutate(
    +    age_cat = age_categories(
    +      age_years, 
    +      breakers = c(0, 6, 11, 16, 21, 31, 41, 51, 61, 71)))
    +
    +# show table
    +table(linelist$age_cat, useNA = "always")
    +
    +
    
    +  0-5  6-10 11-15 16-20 21-30 31-40 41-50 51-60 61-70   71+  <NA> 
    + 1469  1195  1040   770  1149   547   231    70    24     6   107 
    +
    +
    +

    You can adjust how the labels are displayed with separator =. The default is “-”

    +

    You can adjust how the top numbers are handled, with the ceiling = arguemnt. To set an upper cut-off set ceiling = TRUE. In this use, the highest break value provided is a “ceiling” and a category “XX+” is not created. Any values above highest break value (or to upper =, if defined) are categorized as NA. Below is an example with ceiling = TRUE, so that there is no category of XX+ and values above 70 (the highest break value) are assigned as NA.

    +
    +
    # With ceiling set to TRUE
    +##########################
    +linelist <- linelist %>% 
    +  mutate(
    +    age_cat = age_categories(
    +      age_years, 
    +      breakers = c(0, 5, 10, 15, 20, 30, 40, 50, 60, 70),
    +      ceiling = TRUE)) # 70 is ceiling, all above become NA
    +
    +# show table
    +table(linelist$age_cat, useNA = "always")
    +
    +
    
    +  0-4   5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-70  <NA> 
    + 1227  1223  1048   827  1216   597   251    78    28   113 
    +
    +
    +

    Alternatively, instead of breakers =, you can provide all of lower =, upper =, and by =:

    +
      +
    • lower = The lowest number you want considered - default is 0
      +
    • +
    • upper = The highest number you want considered
      +
    • +
    • by = The number of years between groups
    • +
    +
    +
    linelist <- linelist %>% 
    +  mutate(
    +    age_cat = age_categories(
    +      age_years, 
    +      lower = 0,
    +      upper = 100,
    +      by = 10))
    +
    +# show table
    +table(linelist$age_cat, useNA = "always")
    +
    +
    
    +  0-9 10-19 20-29 30-39 40-49 50-59 60-69 70-79 80-89 90-99  100+  <NA> 
    + 2450  1875  1216   597   251    78    27     6     1     0     0   107 
    +
    +
    +

    See the function’s Help page for more details (enter ?age_categories in the R console).

    + +
    +
    +

    cut()

    +

    cut() is a base R alternative to age_categories(), but I think you will see why age_categories() was developed to simplify this process. Some notable differences from age_categories() are:

    +
      +
    • You do not need to install/load another package
    • +
    • You can specify whether groups are open/closed on the right/left
      +
    • +
    • You must provide accurate labels yourself
    • +
    • If you want 0 included in the lowest group you must specify this
    • +
    +

    The basic syntax within cut() is to first provide the numeric column to be cut (age_years), and then the breaks argument, which is a numeric vector c() of break points. Using cut(), the resulting column is an ordered factor.

    +

    By default, the categorization occurs so that the right/upper side is “open” and inclusive (and the left/lower side is “closed” or exclusive). This is the opposite behavior from the age_categories() function. The default labels use the notation “(A, B]”, which means A is not included but B is.Reverse this behavior by providing the right = TRUE argument.

    +

    Thus, by default, “0” values are excluded from the lowest group, and categorized as NA! “0” values could be infants coded as age 0 so be careful! To change this, add the argument include.lowest = TRUE so that any “0” values will be included in the lowest group. The automatically-generated label for the lowest category will then be “[A],B]”. Note that if you include the include.lowest = TRUE argument and right = TRUE, the extreme inclusion will now apply to the highest break point value and category, not the lowest.

    +

    You can provide a vector of customized labels using the labels = argument. As these are manually written, be very careful to ensure they are accurate! Check your work using cross-tabulation, as described below.

    +

    An example of cut() applied to age_years to make the new variable age_cat is below:

    +
    +
    # Create new variable, by cutting the numeric age variable
    +# lower break is excluded but upper break is included in each category
    +linelist <- linelist %>% 
    +  mutate(
    +    age_cat = cut(
    +      age_years,
    +      breaks = c(0, 5, 10, 15, 20,
    +                 30, 50, 70, 100),
    +      include.lowest = TRUE         # include 0 in lowest group
    +      ))
    +
    +# tabulate the number of observations per group
    +table(linelist$age_cat, useNA = "always")
    +
    +
    
    +   [0,5]   (5,10]  (10,15]  (15,20]  (20,30]  (30,50]  (50,70] (70,100] 
    +    1469     1195     1040      770     1149      778       94        6 
    +    <NA> 
    +     107 
    +
    +
    +

    Check your work!!! Verify that each age value was assigned to the correct category by cross-tabulating the numeric and category columns. Examine assignment of boundary values (e.g. 15, if neighboring categories are 10-15 and 16-20).

    +
    +
    # Cross tabulation of the numeric and category columns. 
    +table("Numeric Values" = linelist$age_years,   # names specified in table for clarity.
    +      "Categories"     = linelist$age_cat,
    +      useNA = "always")                        # don't forget to examine NA values
    +
    +
                        Categories
    +Numeric Values       [0,5] (5,10] (10,15] (15,20] (20,30] (30,50] (50,70]
    +  0                    136      0       0       0       0       0       0
    +  0.0833333333333333     1      0       0       0       0       0       0
    +  0.25                   2      0       0       0       0       0       0
    +  0.333333333333333      6      0       0       0       0       0       0
    +  0.416666666666667      1      0       0       0       0       0       0
    +  0.5                    6      0       0       0       0       0       0
    +  0.583333333333333      3      0       0       0       0       0       0
    +  0.666666666666667      3      0       0       0       0       0       0
    +  0.75                   3      0       0       0       0       0       0
    +  0.833333333333333      1      0       0       0       0       0       0
    +  0.916666666666667      1      0       0       0       0       0       0
    +  1                    275      0       0       0       0       0       0
    +  1.5                    2      0       0       0       0       0       0
    +  2                    308      0       0       0       0       0       0
    +  3                    246      0       0       0       0       0       0
    +  4                    233      0       0       0       0       0       0
    +  5                    242      0       0       0       0       0       0
    +  6                      0    241       0       0       0       0       0
    +  7                      0    256       0       0       0       0       0
    +  8                      0    239       0       0       0       0       0
    +  9                      0    245       0       0       0       0       0
    +  10                     0    214       0       0       0       0       0
    +  11                     0      0     220       0       0       0       0
    +  12                     0      0     224       0       0       0       0
    +  13                     0      0     191       0       0       0       0
    +  14                     0      0     199       0       0       0       0
    +  15                     0      0     206       0       0       0       0
    +  16                     0      0       0     186       0       0       0
    +  17                     0      0       0     164       0       0       0
    +  18                     0      0       0     141       0       0       0
    +  19                     0      0       0     130       0       0       0
    +  20                     0      0       0     149       0       0       0
    +  21                     0      0       0       0     158       0       0
    +  22                     0      0       0       0     149       0       0
    +  23                     0      0       0       0     125       0       0
    +  24                     0      0       0       0     144       0       0
    +  25                     0      0       0       0     107       0       0
    +  26                     0      0       0       0     100       0       0
    +  27                     0      0       0       0     117       0       0
    +  28                     0      0       0       0      85       0       0
    +  29                     0      0       0       0      82       0       0
    +  30                     0      0       0       0      82       0       0
    +  31                     0      0       0       0       0      68       0
    +  32                     0      0       0       0       0      84       0
    +  33                     0      0       0       0       0      78       0
    +  34                     0      0       0       0       0      58       0
    +  35                     0      0       0       0       0      58       0
    +  36                     0      0       0       0       0      33       0
    +  37                     0      0       0       0       0      46       0
    +  38                     0      0       0       0       0      45       0
    +  39                     0      0       0       0       0      45       0
    +  40                     0      0       0       0       0      32       0
    +  41                     0      0       0       0       0      34       0
    +  42                     0      0       0       0       0      26       0
    +  43                     0      0       0       0       0      31       0
    +  44                     0      0       0       0       0      24       0
    +  45                     0      0       0       0       0      27       0
    +  46                     0      0       0       0       0      25       0
    +  47                     0      0       0       0       0      16       0
    +  48                     0      0       0       0       0      21       0
    +  49                     0      0       0       0       0      15       0
    +  50                     0      0       0       0       0      12       0
    +  51                     0      0       0       0       0       0      13
    +  52                     0      0       0       0       0       0       7
    +  53                     0      0       0       0       0       0       4
    +  54                     0      0       0       0       0       0       6
    +  55                     0      0       0       0       0       0       9
    +  56                     0      0       0       0       0       0       7
    +  57                     0      0       0       0       0       0       9
    +  58                     0      0       0       0       0       0       6
    +  59                     0      0       0       0       0       0       5
    +  60                     0      0       0       0       0       0       4
    +  61                     0      0       0       0       0       0       2
    +  62                     0      0       0       0       0       0       1
    +  63                     0      0       0       0       0       0       5
    +  64                     0      0       0       0       0       0       1
    +  65                     0      0       0       0       0       0       5
    +  66                     0      0       0       0       0       0       3
    +  67                     0      0       0       0       0       0       2
    +  68                     0      0       0       0       0       0       1
    +  69                     0      0       0       0       0       0       3
    +  70                     0      0       0       0       0       0       1
    +  72                     0      0       0       0       0       0       0
    +  73                     0      0       0       0       0       0       0
    +  76                     0      0       0       0       0       0       0
    +  84                     0      0       0       0       0       0       0
    +  <NA>                   0      0       0       0       0       0       0
    +                    Categories
    +Numeric Values       (70,100] <NA>
    +  0                         0    0
    +  0.0833333333333333        0    0
    +  0.25                      0    0
    +  0.333333333333333         0    0
    +  0.416666666666667         0    0
    +  0.5                       0    0
    +  0.583333333333333         0    0
    +  0.666666666666667         0    0
    +  0.75                      0    0
    +  0.833333333333333         0    0
    +  0.916666666666667         0    0
    +  1                         0    0
    +  1.5                       0    0
    +  2                         0    0
    +  3                         0    0
    +  4                         0    0
    +  5                         0    0
    +  6                         0    0
    +  7                         0    0
    +  8                         0    0
    +  9                         0    0
    +  10                        0    0
    +  11                        0    0
    +  12                        0    0
    +  13                        0    0
    +  14                        0    0
    +  15                        0    0
    +  16                        0    0
    +  17                        0    0
    +  18                        0    0
    +  19                        0    0
    +  20                        0    0
    +  21                        0    0
    +  22                        0    0
    +  23                        0    0
    +  24                        0    0
    +  25                        0    0
    +  26                        0    0
    +  27                        0    0
    +  28                        0    0
    +  29                        0    0
    +  30                        0    0
    +  31                        0    0
    +  32                        0    0
    +  33                        0    0
    +  34                        0    0
    +  35                        0    0
    +  36                        0    0
    +  37                        0    0
    +  38                        0    0
    +  39                        0    0
    +  40                        0    0
    +  41                        0    0
    +  42                        0    0
    +  43                        0    0
    +  44                        0    0
    +  45                        0    0
    +  46                        0    0
    +  47                        0    0
    +  48                        0    0
    +  49                        0    0
    +  50                        0    0
    +  51                        0    0
    +  52                        0    0
    +  53                        0    0
    +  54                        0    0
    +  55                        0    0
    +  56                        0    0
    +  57                        0    0
    +  58                        0    0
    +  59                        0    0
    +  60                        0    0
    +  61                        0    0
    +  62                        0    0
    +  63                        0    0
    +  64                        0    0
    +  65                        0    0
    +  66                        0    0
    +  67                        0    0
    +  68                        0    0
    +  69                        0    0
    +  70                        0    0
    +  72                        1    0
    +  73                        3    0
    +  76                        1    0
    +  84                        1    0
    +  <NA>                      0  107
    +
    +
    +

    Re-labeling NA values

    +

    You may want to assign NA values a label such as “Missing”. Because the new column is class Factor (restricted values), you cannot simply mutate it with replace_na(), as this value will be rejected. Instead, use fct_na_value_to_level() from forcats as explained in the Factors page.

    +
    +
    linelist <- linelist %>% 
    +  
    +  # cut() creates age_cat, automatically of class Factor      
    +  mutate(age_cat = cut(
    +    age_years,
    +    breaks = c(0, 5, 10, 15, 20, 30, 50, 70, 100),          
    +    right = FALSE,
    +    include.lowest = TRUE,        
    +    labels = c("0-4", "5-9", "10-14", "15-19", "20-29", "30-49", "50-69", "70-100")),
    +         
    +    # make missing values explicit
    +    age_cat = fct_na_value_to_level(
    +      age_cat,
    +      level = "Missing age")  # you can specify the label
    +  )    
    +
    +# table to view counts
    +table(linelist$age_cat, useNA = "always")
    +
    +
    
    +        0-4         5-9       10-14       15-19       20-29       30-49 
    +       1227        1223        1048         827        1216         848 
    +      50-69      70-100 Missing age        <NA> 
    +        105           7         107           0 
    +
    +
    +

    Quickly make breaks and labels

    +

    For a fast way to make breaks and label vectors, use something like below. See the R basics page for references on seq() and rep().

    +
    +
    # Make break points from 0 to 90 by 5
    +age_seq = seq(from = 0, to = 90, by = 5)
    +age_seq
    +
    +# Make labels for the above categories, assuming default cut() settings
    +age_labels = paste0(age_seq + 1, "-", age_seq + 5)
    +age_labels
    +
    +# check that both vectors are the same length
    +length(age_seq) == length(age_labels)
    +
    +

    Read more about cut() in its Help page by entering ?cut in the R console.

    +
    +
    +

    Quantile breaks

    +

    In common understanding, “quantiles” or “percentiles” typically refer to a value below which a proportion of values fall. For example, the 95th percentile of ages in linelist would be the age below which 95% of the age fall.

    +

    However in common speech, “quartiles” and “deciles” can also refer to the groups of data as equally divided into 4, or 10 groups (note there will be one more break point than group).

    +

    To get quantile break points, you can use quantile() from the stats package from base R. You provide a numeric vector (e.g. a column in a dataset) and vector of numeric probability values ranging from 0 to 1.0. The break points are returned as a numeric vector. Explore the details of the statistical methodologies by entering ?quantile.

    +
      +
    • If your input numeric vector has any missing values it is best to set na.rm = TRUE
      +
    • +
    • Set names = FALSE to get an un-named numeric vector
    • +
    +
    +
    quantile(linelist$age_years,               # specify numeric vector to work on
    +  probs = c(0, .25, .50, .75, .90, .95),   # specify the percentiles you want
    +  na.rm = TRUE)                            # ignore missing values 
    +
    +
     0% 25% 50% 75% 90% 95% 
    +  0   6  13  23  33  41 
    +
    +
    +

    You can use the results of quantile() as break points in age_categories() or cut(). Below we create a new column deciles using cut() where the breaks are defined using quantiles() on age_years. Below, we display the results using tabyl() from janitor so you can see the percentages (see the Descriptive tables page). Note how they are not exactly 10% in each group.

    +
    +
    linelist %>%                                # begin with linelist
    +  mutate(deciles = cut(age_years,           # create new column decile as cut() on column age_years
    +    breaks = quantile(                      # define cut breaks using quantile()
    +      age_years,                               # operate on age_years
    +      probs = seq(0, 1, by = 0.1),             # 0.0 to 1.0 by 0.1
    +      na.rm = TRUE),                           # ignore missing values
    +    include.lowest = TRUE)) %>%             # for cut() include age 0
    +  janitor::tabyl(deciles)                   # pipe to table to display
    +
    +
     deciles   n    percent valid_percent
    +   [0,2] 748 0.11319613    0.11505922
    +   (2,5] 721 0.10911017    0.11090601
    +   (5,7] 497 0.07521186    0.07644978
    +  (7,10] 698 0.10562954    0.10736810
    + (10,13] 635 0.09609564    0.09767728
    + (13,17] 755 0.11425545    0.11613598
    + (17,21] 578 0.08746973    0.08890940
    + (21,26] 625 0.09458232    0.09613906
    + (26,33] 596 0.09019370    0.09167820
    + (33,84] 648 0.09806295    0.09967697
    +    <NA> 107 0.01619249            NA
    +
    +
    +
    +
    +

    Evenly-sized groups

    +

    Another tool to make numeric groups is the the dplyr function ntile(), which attempts to break your data into n evenly-sized groups - but be aware that unlike with quantile() the same value could appear in more than one group. Provide the numeric vector and then the number of groups. The values in the new column created is just group “numbers” (e.g. 1 to 10), not the range of values themselves as when using cut().

    +
    +
    # make groups with ntile()
    +ntile_data <- linelist %>% 
    +  mutate(even_groups = ntile(age_years, 10))
    +
    +# make table of counts and proportions by group
    +ntile_table <- ntile_data %>% 
    +  janitor::tabyl(even_groups)
    +  
    +# attach min/max values to demonstrate ranges
    +ntile_ranges <- ntile_data %>% 
    +  group_by(even_groups) %>% 
    +  summarise(
    +    min = min(age_years, na.rm=T),
    +    max = max(age_years, na.rm=T)
    +  )
    +
    +
    Warning: There were 2 warnings in `summarise()`.
    +The first warning was:
    +ℹ In argument: `min = min(age_years, na.rm = T)`.
    +ℹ In group 11: `even_groups = NA`.
    +Caused by warning in `min()`:
    +! no non-missing arguments to min; returning Inf
    +ℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.
    +
    +
    # combine and print - note that values are present in multiple groups
    +left_join(ntile_table, ntile_ranges, by = "even_groups")
    +
    +
     even_groups   n    percent valid_percent min  max
    +           1 651 0.09851695    0.10013844   0    2
    +           2 650 0.09836562    0.09998462   2    5
    +           3 650 0.09836562    0.09998462   5    7
    +           4 650 0.09836562    0.09998462   7   10
    +           5 650 0.09836562    0.09998462  10   13
    +           6 650 0.09836562    0.09998462  13   17
    +           7 650 0.09836562    0.09998462  17   21
    +           8 650 0.09836562    0.09998462  21   26
    +           9 650 0.09836562    0.09998462  26   33
    +          10 650 0.09836562    0.09998462  33   84
    +          NA 107 0.01619249            NA Inf -Inf
    +
    +
    + +
    +
    +

    case_when()

    +

    It is possible to use the dplyr function case_when() to create categories from a numeric column, but it is easier to use age_categories() from epikit or cut() because these will create an ordered factor automatically.

    +

    If using case_when(), please review the proper use as described earlier in the Re-code values section of this page. Also be aware that all right-hand side values must be of the same class. Thus, if you want NA on the right-side you should either write “Missing” or use the special NA value NA_character_.

    +
    +
    +

    Add to pipe chain

    +

    Below, code to create two categorical age columns is added to the cleaning pipe chain:

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +
    +# begin cleaning pipe chain
    +###########################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome) %>% 
    +    
    +    # remove column
    +    select(-c(row_num, merged_header, x28)) %>% 
    +  
    +    # de-duplicate
    +    distinct() %>% 
    +
    +    # add column
    +    mutate(bmi = wt_kg / (ht_cm/100)^2) %>%     
    +
    +    # convert class of columns
    +    mutate(across(contains("date"), as.Date), 
    +           generation = as.numeric(generation),
    +           age        = as.numeric(age)) %>% 
    +    
    +    # add column: delay to hospitalisation
    +    mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% 
    +    
    +    # clean values of hospital column
    +    mutate(hospital = recode(hospital,
    +                      # OLD = NEW
    +                      "Mitylira Hopital"  = "Military Hospital",
    +                      "Mitylira Hospital" = "Military Hospital",
    +                      "Military Hopital"  = "Military Hospital",
    +                      "Port Hopital"      = "Port Hospital",
    +                      "Central Hopital"   = "Central Hospital",
    +                      "other"             = "Other",
    +                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
    +                      )) %>% 
    +    
    +    mutate(hospital = replace_na(hospital, "Missing")) %>% 
    +
    +    # create age_years column (from age and age_unit)
    +    mutate(age_years = case_when(
    +          age_unit == "years" ~ age,
    +          age_unit == "months" ~ age/12,
    +          is.na(age_unit) ~ age)) %>% 
    +  
    +    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    +    ###################################################   
    +    mutate(
    +          # age categories: custom
    +          age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),
    +        
    +          # age categories: 0 to 85 by 5s
    +          age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5)))
    +
    + +
    +
    +
    +

    8.10 Add rows

    +
    +

    One-by-one

    +

    Adding rows one-by-one manually is tedious but can be done with add_row() from dplyr. Remember that each column must contain values of only one class (either character, numeric, logical, etc.). So adding a row requires nuance to maintain this.

    +
    +
    linelist <- linelist %>% 
    +  add_row(row_num = 666,
    +          case_id = "abc",
    +          generation = 4,
    +          `infection date` = as.Date("2020-10-10"),
    +          .before = 2)
    +
    +

    Use .before and .after. to specify the placement of the row you want to add. .before = 3 will put the new row before the current 3rd row. The default behavior is to add the row to the end. Columns not specified will be left empty (NA).

    +

    The new row number may look strange (“…23”) but the row numbers in the pre-existing rows have changed. So if using the command twice, examine/test the insertion carefully.

    +

    If a class you provide is off you will see an error like this:

    +
    Error: Can't combine ..1$infection date <date> and ..2$infection date <character>.
    +

    (when inserting a row with a date value, remember to wrap the date in the function as.Date() like as.Date("2020-10-10")).

    +
    +
    +

    Bind rows

    +

    To combine datasets together by binding the rows of one dataframe to the bottom of another data frame, you can use bind_rows() from dplyr. This is explained in more detail in the page Joining data.

    + + + +
    +
    +
    +

    8.11 Filter rows

    +

    A typical cleaning step after you have cleaned the columns and re-coded values is to filter the data frame for specific rows using the dplyr verb filter().

    +

    Within filter(), specify the logic that must be TRUE for a row in the dataset to be kept. Below we show how to filter rows based on simple and complex logical conditions.

    + +
    +

    Simple filter

    +

    This simple example re-defines the dataframe linelist as itself, having filtered the rows to meet a logical condition. Only the rows where the logical statement within the parentheses evaluates to TRUE are kept.

    +

    In this example, the logical statement is gender == "f", which is asking whether the value in the column gender is equal to “f” (case sensitive).

    +

    Before the filter is applied, the number of rows in linelist is nrow(linelist).

    +
    +
    linelist <- linelist %>% 
    +  filter(gender == "f")   # keep only rows where gender is equal to "f"
    +
    +

    After the filter is applied, the number of rows in linelist is linelist %>% filter(gender == "f") %>% nrow().

    +
    +
    +

    Filter out missing values

    +

    It is fairly common to want to filter out rows that have missing values. Resist the urge to write filter(!is.na(column) & !is.na(column)) and instead use the tidyr function that is custom-built for this purpose: drop_na(). If run with empty parentheses, it removes rows with any missing values. Alternatively, you can provide names of specific columns to be evaluated for missingness, or use the “tidyselect” helper functions described above.

    +
    +
    linelist %>% 
    +  drop_na(case_id, age_years)  # drop rows with missing values for case_id or age_years
    +
    +

    See the page on Missing data for many techniques to analyse and manage missingness in your data.

    +
    +
    +

    Filter by row number

    +

    In a data frame or tibble, each row will usually have a “row number” that (when seen in R Viewer) appears to the left of the first column. It is not itself a true column in the data, but it can be used in a filter() statement.

    +

    To filter based on “row number”, you can use the dplyr function row_number() with open parentheses as part of a logical filtering statement. Often you will use the %in% operator and a range of numbers as part of that logical statement, as shown below. To see the first N rows, you can also use the special dplyr function head().

    +
    +
    # View first 100 rows
    +linelist %>% head(100)     # or use tail() to see the n last rows
    +
    +# Show row 5 only
    +linelist %>% filter(row_number() == 5)
    +
    +# View rows 2 through 20, and three specific columns
    +linelist %>% filter(row_number() %in% 2:20) %>% select(date_onset, outcome, age)
    +
    +

    You can also convert the row numbers to a true column by piping your data frame to the tibble function rownames_to_column() (do not put anything in the parentheses).

    + +
    +
    +

    Complex filter

    +

    More complex logical statements can be constructed using parentheses ( ), OR |, negate !, %in%, and AND & operators. An example is below:

    +

    Note: You can use the ! operator in front of a logical criteria to negate it. For example, !is.na(column) evaluates to true if the column value is not missing. Likewise !column %in% c("a", "b", "c") evaluates to true if the column value is not in the vector.

    +
    +

    Examine the data

    +

    Below is a simple one-line command to create a histogram of onset dates. See that a second smaller outbreak from 2012-2013 is also included in this raw dataset. For our analyses, we want to remove entries from this earlier outbreak.

    +
    +
    hist(linelist$date_onset, breaks = 50)
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    How filters handle missing numeric and date values

    +

    Can we just filter by date_onset to rows after June 2013? Caution! Applying the code filter(date_onset > as.Date("2013-06-01"))) would remove any rows in the later epidemic with a missing date of onset!

    +

    DANGER: Filtering to greater than (>) or less than (<) a date or number can remove any rows with missing values (NA)! This is because NA is treated as infinitely large and small.

    +

    (See the page on Working with dates for more information on working with dates and the package lubridate)

    +
    +
    +

    Design the filter

    +

    Examine a cross-tabulation to make sure we exclude only the correct rows:

    +
    +
    table(Hospital  = linelist$hospital,                     # hospital name
    +      YearOnset = lubridate::year(linelist$date_onset),  # year of date_onset
    +      useNA     = "always")                              # show missing values
    +
    +
                                          YearOnset
    +Hospital                               2012 2013 2014 2015 <NA>
    +  Central Hospital                        0    0  351   99   18
    +  Hospital A                            229   46    0    0   15
    +  Hospital B                            227   47    0    0   15
    +  Military Hospital                       0    0  676  200   34
    +  Missing                                 0    0 1117  318   77
    +  Other                                   0    0  684  177   46
    +  Port Hospital                           9    1 1372  347   75
    +  St. Mark's Maternity Hospital (SMMH)    0    0  322   93   13
    +  <NA>                                    0    0    0    0    0
    +
    +
    +

    What other criteria can we filter on to remove the first outbreak (in 2012 & 2013) from the dataset? We see that:

    +
      +
    • The first epidemic in 2012 & 2013 occurred at Hospital A, Hospital B, and that there were also 10 cases at Port Hospital.
      +
    • +
    • Hospitals A & B did not have cases in the second epidemic, but Port Hospital did.
    • +
    +

    We want to exclude:

    +
      +
    • The rows with onset in 2012 and 2013 at either hospital A, B, or Port: nrow(linelist %>% filter(hospital %in% c("Hospital A", "Hospital B") | date_onset < as.Date("2013-06-01")))

      +
        +
      • Exclude rows with onset in 2012 and 2013 nrow(linelist %>% filter(date_onset < as.Date("2013-06-01")))
      • +
      • Exclude rows from Hospitals A & B with missing onset dates
        +nrow(linelist %>% filter(hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))
      • +
      • Do not exclude other rows with missing onset dates.
        +nrow(linelist %>% filter(!hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))
      • +
    • +
    +

    We start with a linelist of nrow(linelist)`. Here is our filter statement:

    +
    +
    linelist <- linelist %>% 
    +  # keep rows where onset is after 1 June 2013 OR where onset is missing and it was a hospital OTHER than Hospital A or B
    +  filter(date_onset > as.Date("2013-06-01") | (is.na(date_onset) & !hospital %in% c("Hospital A", "Hospital B")))
    +
    +nrow(linelist)
    +
    +
    [1] 6019
    +
    +
    +

    When we re-make the cross-tabulation, we see that Hospitals A & B are removed completely, and the 10 Port Hospital cases from 2012 & 2013 are removed, and all other values are the same - just as we wanted.

    +
    +
    table(Hospital  = linelist$hospital,                     # hospital name
    +      YearOnset = lubridate::year(linelist$date_onset),  # year of date_onset
    +      useNA     = "always")                              # show missing values
    +
    +
                                          YearOnset
    +Hospital                               2014 2015 <NA>
    +  Central Hospital                      351   99   18
    +  Military Hospital                     676  200   34
    +  Missing                              1117  318   77
    +  Other                                 684  177   46
    +  Port Hospital                        1372  347   75
    +  St. Mark's Maternity Hospital (SMMH)  322   93   13
    +  <NA>                                    0    0    0
    +
    +
    +

    Multiple statements can be included within one filter command (separated by commas), or you can always pipe to a separate filter() command for clarity.

    +

    Note: some readers may notice that it would be easier to just filter by date_hospitalisation because it is 100% complete with no missing values. This is true. But date_onset is used for purposes of demonstrating a complex filter.

    +
    +
    +
    +

    Standalone

    +

    Filtering can also be done as a stand-alone command (not part of a pipe chain). Like other dplyr verbs, in this case the first argument must be the dataset itself.

    +
    +
    # dataframe <- filter(dataframe, condition(s) for rows to keep)
    +
    +linelist <- filter(linelist, !is.na(case_id))
    +
    +

    You can also use base R to subset using square brackets which reflect the [rows, columns] that you want to retain.

    +
    +
    # dataframe <- dataframe[row conditions, column conditions] (blank means keep all)
    +
    +linelist <- linelist[!is.na(case_id), ]
    +
    +
    +
    +

    Quickly review records

    +

    Often you want to quickly review a few records, for only a few columns. The base R function View() will print a data frame for viewing in your RStudio.

    +

    View the linelist in RStudio:

    +
    +
    View(linelist)
    +
    +

    Here are two examples of viewing specific cells (specific rows, and specific columns):

    +

    With dplyr functions filter() and select():

    +

    Within View(), pipe the dataset to filter() to keep certain rows, and then to select() to keep certain columns. For example, to review onset and hospitalization dates of 3 specific cases:

    +
    +
    View(linelist %>%
    +       filter(case_id %in% c("11f8ea", "76b97a", "47a5f5")) %>%
    +       select(date_onset, date_hospitalisation))
    +
    +

    You can achieve the same with base R syntax, using brackets [ ] to subset you want to see.

    +
    +
    View(linelist[linelist$case_id %in% c("11f8ea", "76b97a", "47a5f5"), c("date_onset", "date_hospitalisation")])
    +
    +
    +

    Add to pipe chain

    +
    +
    # CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
    +##################################################################################
    +
    +# begin cleaning pipe chain
    +###########################
    +linelist <- linelist_raw %>%
    +    
    +    # standardize column name syntax
    +    janitor::clean_names() %>% 
    +    
    +    # manually re-name columns
    +           # NEW name             # OLD name
    +    rename(date_infection       = infection_date,
    +           date_hospitalisation = hosp_date,
    +           date_outcome         = date_of_outcome) %>% 
    +    
    +    # remove column
    +    select(-c(row_num, merged_header, x28)) %>% 
    +  
    +    # de-duplicate
    +    distinct() %>% 
    +
    +    # add column
    +    mutate(bmi = wt_kg / (ht_cm/100)^2) %>%     
    +
    +    # convert class of columns
    +    mutate(across(contains("date"), as.Date), 
    +           generation = as.numeric(generation),
    +           age        = as.numeric(age)) %>% 
    +    
    +    # add column: delay to hospitalisation
    +    mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% 
    +    
    +    # clean values of hospital column
    +    mutate(hospital = recode(hospital,
    +                      # OLD = NEW
    +                      "Mitylira Hopital"  = "Military Hospital",
    +                      "Mitylira Hospital" = "Military Hospital",
    +                      "Military Hopital"  = "Military Hospital",
    +                      "Port Hopital"      = "Port Hospital",
    +                      "Central Hopital"   = "Central Hospital",
    +                      "other"             = "Other",
    +                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
    +                      )) %>% 
    +    
    +    mutate(hospital = replace_na(hospital, "Missing")) %>% 
    +
    +    # create age_years column (from age and age_unit)
    +    mutate(age_years = case_when(
    +          age_unit == "years" ~ age,
    +          age_unit == "months" ~ age/12,
    +          is.na(age_unit) ~ age)) %>% 
    +  
    +    mutate(
    +          # age categories: custom
    +          age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),
    +        
    +          # age categories: 0 to 85 by 5s
    +          age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5))) %>% 
    +    
    +    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    +    ###################################################
    +    filter(
    +          # keep only rows where case_id is not missing
    +          !is.na(case_id),  
    +          
    +          # also filter to keep only the second outbreak
    +          date_onset > as.Date("2013-06-01") | (is.na(date_onset) & !hospital %in% c("Hospital A", "Hospital B")))
    +
    + + + +
    +
    +
    +
    +

    8.12 Row-wise calculations

    +

    If you want to perform a calculation within a row, you can use rowwise() from dplyr. See this online vignette on row-wise calculations. For example, this code applies rowwise() and then creates a new column that sums the number of the specified symptom columns that have value “yes”, for each row in the linelist. The columns are specified within sum() by name within a vector c(). rowwise() is essentially a special kind of group_by(), so it is best to use ungroup() when you are done (page on Grouping data).

    +
    +
    linelist %>%
    +  rowwise() %>%
    +  mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == "yes"), na.rm =T) %>% 
    +  ungroup() %>% 
    +  select(fever, chills, cough, aches, vomit, num_symptoms) # for display
    +
    +
    # A tibble: 5,888 × 6
    +   fever chills cough aches vomit num_symptoms
    +   <chr> <chr>  <chr> <chr> <chr>        <int>
    + 1 no    no     yes   no    yes              2
    + 2 <NA>  <NA>   <NA>  <NA>  <NA>            NA
    + 3 <NA>  <NA>   <NA>  <NA>  <NA>            NA
    + 4 no    no     no    no    no               0
    + 5 no    no     yes   no    yes              2
    + 6 no    no     yes   no    yes              2
    + 7 <NA>  <NA>   <NA>  <NA>  <NA>            NA
    + 8 no    no     yes   no    yes              2
    + 9 no    no     yes   no    yes              2
    +10 no    no     yes   no    no               1
    +# ℹ 5,878 more rows
    +
    +
    +

    As you specify the column to evaluate, you may want to use the “tidyselect” helper functions described in the select() section of this page. You just have to make one adjustment (because you are not using them within a dplyr function like select() or summarise()).

    +

    Put the column-specification criteria within the dplyr function c_across(). This is because c_across (documentation) is designed to work with rowwise() specifically. For example, the following code:

    +
      +
    • Applies rowwise() so the following operation (sum()) is applied within each row (not summing entire columns)
    • +
    • Creates new column num_NA_dates, defined for each row as the number of columns (with name containing “date”) for which is.na() evaluated to TRUE (they are missing data)
    • +
    • ungroup() to remove the effects of rowwise() for subsequent steps
    • +
    +
    +
    linelist %>%
    +  rowwise() %>%
    +  mutate(num_NA_dates = sum(is.na(c_across(contains("date"))))) %>% 
    +  ungroup() %>% 
    +  select(num_NA_dates, contains("date")) # for display
    +
    +
    # A tibble: 5,888 × 5
    +   num_NA_dates date_infection date_onset date_hospitalisation date_outcome
    +          <int> <date>         <date>     <date>               <date>      
    + 1            1 2014-05-08     2014-05-13 2014-05-15           NA          
    + 2            1 NA             2014-05-13 2014-05-14           2014-05-18  
    + 3            1 NA             2014-05-16 2014-05-18           2014-05-30  
    + 4            1 2014-05-04     2014-05-18 2014-05-20           NA          
    + 5            0 2014-05-18     2014-05-21 2014-05-22           2014-05-29  
    + 6            0 2014-05-03     2014-05-22 2014-05-23           2014-05-24  
    + 7            0 2014-05-22     2014-05-27 2014-05-29           2014-06-01  
    + 8            0 2014-05-28     2014-06-02 2014-06-03           2014-06-07  
    + 9            1 NA             2014-06-05 2014-06-06           2014-06-18  
    +10            1 NA             2014-06-05 2014-06-07           2014-06-09  
    +# ℹ 5,878 more rows
    +
    +
    +

    You could also provide other functions, such as max() to get the latest or most recent date for each row:

    +
    +
    linelist %>%
    +  rowwise() %>%
    +  mutate(latest_date = max(c_across(contains("date")), na.rm=T)) %>% 
    +  ungroup() %>% 
    +  select(latest_date, contains("date"))  # for display
    +
    +
    # A tibble: 5,888 × 5
    +   latest_date date_infection date_onset date_hospitalisation date_outcome
    +   <date>      <date>         <date>     <date>               <date>      
    + 1 2014-05-15  2014-05-08     2014-05-13 2014-05-15           NA          
    + 2 2014-05-18  NA             2014-05-13 2014-05-14           2014-05-18  
    + 3 2014-05-30  NA             2014-05-16 2014-05-18           2014-05-30  
    + 4 2014-05-20  2014-05-04     2014-05-18 2014-05-20           NA          
    + 5 2014-05-29  2014-05-18     2014-05-21 2014-05-22           2014-05-29  
    + 6 2014-05-24  2014-05-03     2014-05-22 2014-05-23           2014-05-24  
    + 7 2014-06-01  2014-05-22     2014-05-27 2014-05-29           2014-06-01  
    + 8 2014-06-07  2014-05-28     2014-06-02 2014-06-03           2014-06-07  
    + 9 2014-06-18  NA             2014-06-05 2014-06-06           2014-06-18  
    +10 2014-06-09  NA             2014-06-05 2014-06-07           2014-06-09  
    +# ℹ 5,878 more rows
    +
    +
    +
    +
    +

    8.13 Arrange and sort

    +

    Use the dplyr function arrange() to sort or order the rows by column values.

    +

    Simple list the columns in the order they should be sorted on. Specify .by_group = TRUE if you want the sorting to to first occur by any groupings applied to the data (see page on Grouping data).

    +

    By default, column will be sorted in “ascending” order (which applies to numeric and also to character columns). You can sort a variable in “descending” order by wrapping it with desc().

    +

    Sorting data with arrange() is particularly useful when making Tables for presentation, using slice() to take the “top” rows per group, or setting factor level order by order of appearance.

    +

    For example, to sort the our linelist rows by hospital, then by date_onset in descending order, we would use:

    +
    +
    linelist %>% 
    +   arrange(hospital, desc(date_onset))
    +
    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/cleaning.qmd b/new_pages/cleaning.qmd index 618dc434..0cfd7fd2 100644 --- a/new_pages/cleaning.qmd +++ b/new_pages/cleaning.qmd @@ -27,7 +27,7 @@ Function | Utility | Package `clean_names()`|standardize the syntax of column names|**janitor** `as.character()`, `as.numeric()`, `as.Date()`, etc.|convert the class of a column|**base** R `across()`|transform multiple columns at one time|**dplyr** -**tidyselect** functions|use logic to select columns|**tidyselect** +**tidyselect** functions|[use logic to select columns](cleaning.qmd#clean_tidyselect)|**tidyselect** `filter()`|keep certain rows|**dplyr** `distinct()`|de-duplicate rows|**dplyr** `rowwise()`|operations by/within each row|**dplyr** @@ -69,11 +69,11 @@ Such chains utilize **dplyr** "verb" functions and the **magrittr** pipe operato In a cleaning pipeline the order of the steps is important. Cleaning steps might include: -* Importing of data -* Column names cleaned or changed -* De-duplication -* Column creation and transformation (e.g. re-coding or standardising values) -* Rows filtered or added +* Importing of data +* Column names cleaned or changed +* De-duplication +* Column creation and transformation (e.g. re-coding or standardising values) +* Rows filtered or added @@ -132,7 +132,7 @@ DT::datatable(head(linelist_raw,50), rownames = FALSE, options = list(pageLength ``` ### Review {.unnumbered} -You can use the function `skim()` from the package **skimr** to get an overview of the entire dataframe (see page on [Descriptive tables](tables_descriptive.qmd) for more info). Columns are summarised by class/type such as character, numeric. Note: "POSIXct" is a type of raw date class (see [Working with dates](dates.qmd). +You can use the function `skim()` from the package **skimr** to get an overview of the entire dataframe (see page on [Descriptive tables](tables_descriptive.qmd) for more info). Columns are summarised by class/type such as character, numeric. Note: "POSIXct" is a type of raw date class (see [Working with dates](dates.qmd)). ```{r, eval=F} @@ -166,31 +166,31 @@ Other statistical software such as SAS and STATA use *"labels"* that co-exist as As R column names are used very often, so they must have "clean" syntax. We suggest the following: * Short names -* No spaces (replace with underscores _ ) -* No unusual characters (&, #, <, >, ...) +* No spaces (replace with underscores _ ) +* No unusual characters (&, #, <, >, ...) * Similar style nomenclature (e.g. all date columns named like **date_**onset, **date_**report, **date_**death...) The columns names of `linelist_raw` are printed below using `names()` from **base** R. We can see that initially: -* Some names contain spaces (e.g. `infection date`) -* Different naming patterns are used for dates (`date onset` vs. `infection date`) -* There must have been a *merged header* across the two last columns in the .xlsx. We know this because the name of two merged columns ("merged_header") was assigned by R to the first column, and the second column was assigned a placeholder name "...28" (as it was then empty and is the 28th column). +* Some names contain spaces (e.g. `infection date`) +* Different naming patterns are used for dates (`date onset` vs. `infection date`) +* There must have been a *merged header* across the two last columns in the .xlsx. We know this because the name of two merged columns ("merged_header") was assigned by R to the first column, and the second column was assigned a placeholder name "...28" (as it was then empty and is the 28th column) ```{r} names(linelist_raw) ``` -**_NOTE:_** To reference a column name that includes spaces, surround the name with back-ticks, for example: linelist$`` ` '\x60infection date\x60'` ``. note that on your keyboard, the back-tick (`) is different from the single quotation mark ('). +**_NOTE:_** To reference a column name that includes spaces, surround the name with back-ticks, for example: linelist$\``infection date`\`. note that on your keyboard, the back-tick (`) is different from the single quotation mark ('). ### Automatic cleaning {.unnumbered} The function `clean_names()` from the package **janitor** standardizes column names and makes them unique by doing the following: -* Converts all names to consist of only underscores, numbers, and letters -* Accented characters are transliterated to ASCII (e.g. german o with umlaut becomes "o", spanish "enye" becomes "n") -* Capitalization preference for the new column names can be specified using the `case = ` argument ("snake" is default, alternatives include "sentence", "title", "small_camel"...) -* You can specify specific name replacements by providing a vector to the `replace = ` argument (e.g. `replace = c(onset = "date_of_onset")`) +* Converts all names to consist of only underscores, numbers, and letters +* Accented characters are transliterated to ASCII (e.g. german o with umlaut becomes "o", spanish "enye" becomes "n") +* Capitalization preference for the new column names can be specified using the `case = ` argument ("snake" is default, alternatives include "sentence", "title", "small_camel"...) +* You can specify specific name replacements by providing a vector to the `replace = ` argument (e.g. `replace = c(onset = "date_of_onset")`) * Here is an online [vignette](https://cran.r-project.org/web/packages/janitor/vignettes/janitor.html#cleaning) Below, the cleaning pipeline begins by using `clean_names()` on the raw linelist. @@ -209,7 +209,7 @@ names(linelist) ### Manual name cleaning {.unnumbered} -Re-naming columns manually is often necessary, even after the standardization step above. Below, re-naming is performed using the `rename()` function from the **dplyr** package, as part of a pipe chain. `rename()` uses the style `NEW = OLD` - the new column name is given before the old column name. +Re-naming columns manually is often necessary, even after the standardization step above. Below, re-naming is performed using the `rename()` function from the **dplyr** package, as part of a pipe chain. `rename()` uses the style `NEW = OLD`, the new column name is given before the old column name. Below, a re-naming command is added to the cleaning pipeline. Spaces have been added strategically to align code for easier reading. @@ -253,8 +253,9 @@ As a shortcut, you can also rename columns within the **dplyr** `select()` and ` ```{r, eval=F} linelist_raw %>% + # rename and KEEP ONLY these columns select(# NEW name # OLD name - date_infection = `infection date`, # rename and KEEP ONLY these columns + date_infection = `infection date`, date_hospitalisation = `hosp date`) ``` @@ -279,9 +280,9 @@ Merged cells in an Excel file are a common occurrence when receiving data. As ex Remind people doing data entry that **human-readable data is not the same as machine-readable data**. Strive to train users about the principles of [**tidy data**](https://r4ds.had.co.nz/tidy-data.html). If at all possible, try to change procedures so that data arrive in a tidy format without merged cells. -* Each variable must have its own column. -* Each observation must have its own row. -* Each value must have its own cell. +* Each variable must have its own column +* Each observation must have its own row +* Each value must have its own cell When using **rio**'s `import()` function, the value in a merged cell will be assigned to the first cell and subsequent cells will be empty. @@ -345,7 +346,7 @@ linelist %>% Here are other "tidyselect" helper functions that also work *within* **dplyr** functions like `select()`, `across()`, and `summarise()`: * `everything()` - all other columns not mentioned -* `last_col()` - the last column +* `last_col()` - the last column * `where()` - applies a function to all columns and selects those which are TRUE * `contains()` - columns containing a character string * example: `select(contains("time"))` @@ -354,9 +355,9 @@ Here are other "tidyselect" helper functions that also work *within* **dplyr** f * `ends_with()` - matches to a specified suffix * example: `select(ends_with("_post"))` * `matches()` - to apply a regular expression (regex) - * example: `select(matches("[pt]al"))` + * example: `select(matches("[pt]al"))` * `num_range()` - a numerical range like x01, x02, x03 -* `any_of()` - matches IF column exists but returns no error if it is not found +* `any_of()` - matches IF column exists but returns no error if it is not found * example: `select(any_of(date_onset, date_death, cardiac_arrest))` In addition, use normal operators such as `c()` to list several columns, `:` for consecutive columns, `!` for opposite, `&` for AND, and `|` for OR. @@ -528,7 +529,7 @@ linelist <- linelist_raw %>% **We recommend using the dplyr function `mutate()` to add a new column, or to modify an existing one.** -Below is an example of creating a new column with `mutate()`. The syntax is: `mutate(new_column_name = value or transformation)` +Below is an example of creating a new column with `mutate()`. The syntax is: `mutate(new_column_name = value or transformation)`. In Stata, this is similar to the command `generate`, but R's `mutate()` can also be used to modify an existing column. @@ -681,16 +682,16 @@ linelist <- linelist %>% ``` * Note that within `across()` we also use the function `where()` as `is.POSIXct` is evaluating to either TRUE or FALSE. -* Note that `is.POSIXct()` is from the package **lubridate**. Other similar "is" functions like `is.character()`, `is.numeric()`, and `is.logical()` are from **base R** +* Note that `is.POSIXct()` is from the package **lubridate**. Other similar "is" functions like `is.character()`, `is.numeric()`, and `is.logical()` are from **base R**. #### `across()` functions {.unnumbered} You can read the documentation with `?across` for details on how to provide functions to `across()`. A few summary points: there are several ways to specify the function(s) to perform on a column and you can even define your own functions: -* You can provide the function name alone (e.g. `mean` or `as.character`) -* You can provide the function in **purrr**-style (e.g. `~ mean(.x, na.rm = TRUE)`) (see [this page](iteration.qmd)) -* You can specify multiple functions by providing a list (e.g. `list(mean = mean, n_miss = ~ sum(is.na(.x))`). - * If you provide multiple functions, multiple transformed columns will be returned per input column, with unique names in the format `col_fn`. You can adjust how the new columns are named with the `.names =` argument using **glue** syntax (see page on [Characters and strings](characters_strings.qmd)) where `{.col}` and `{.fn}` are shorthand for the input column and function. +* You can provide the function name alone (e.g. `mean` or `as.character`) +* You can provide the function in **purrr**-style (e.g. `~ mean(.x, na.rm = TRUE)`) (see [this page](iteration.qmd)) +* You can specify multiple functions by providing a list (e.g. `list(mean = mean, n_miss = ~ sum(is.na(.x))`) + * If you provide multiple functions, multiple transformed columns will be returned per input column, with unique names in the format `col_fn`. You can adjust how the new columns are named with the `.names =` argument using **glue** syntax (see page on [Characters and strings](characters_strings.qmd)) where `{.col}` and `{.fn}` are shorthand for the input column and function Here are a few online resources on using `across()`: [creator Hadley Wickham's thoughts/rationale](https://www.tidyverse.org/blog/2020/04/dplyr-1-0-0-colwise/) @@ -814,10 +815,10 @@ linelist <- linelist_raw %>% Here are a few scenarios where you need to re-code (change) values: -* to edit one specific value (e.g. one date with an incorrect year or format) +* to edit one specific value (e.g. one date with an incorrect year or format) * to reconcile values not spelled the same -* to create a new column of categorical values -* to create a new column of numeric categories (e.g. age categories) +* to create a new column of categorical values +* to create a new column of numeric categories (e.g. age categories) @@ -878,7 +879,7 @@ table(linelist$hospital, useNA = "always") Below we demonstrate how to re-code values in a column using logic and conditions: * Using `replace()`, `ifelse()` and `if_else()` for simple logic -* Using `case_when()` for more complex logic +* Using `case_when()` for more complex logic @@ -889,7 +890,9 @@ Below we demonstrate how to re-code values in a column using logic and condition To re-code with simple logical criteria, you can use `replace()` within `mutate()`. `replace()` is a function from **base** R. Use a logic condition to specify the rows to change . The general syntax is: -`mutate(col_to_change = replace(col_to_change, criteria for rows, new value))`. +```{r, eval = F} +mutate(col_to_change = replace(col_to_change, criteria for rows, new value)) +``` One common situation to use `replace()` is **changing just one value in one row, using an unique row identifier**. Below, the gender is changed to "Female" in the row where the column `case_id` is "2195". @@ -957,7 +960,7 @@ linelist <- linelist %>% ``` -As each row in the data is evaluated, the criteria are applied/evaluated in the order the `case_when()` statements are written - from top-to-bottom. If the top criteria evaluates to `TRUE` for a given row, the RHS value is assigned, and the remaining criteria are not even tested for that row in the data. Thus, it is best to write the most specific criteria first, and the most general last. A data row that does not meet any of the RHS criteria will be assigned `NA`. +As each row in the data is evaluated, the criteria are applied/evaluated in the order the `case_when()` statements are written, from top-to-bottom. If the top criteria evaluates to `TRUE` for a given row, the RHS value is assigned, and the remaining criteria are not even tested for that row in the data. Thus, it is best to write the most specific criteria first, and the most general last. A data row that does not meet any of the RHS criteria will be assigned `NA`. Sometimes, you may with to write a final statement that assigns a value for all other scenarios not described by one of the previous lines. To do this, place `TRUE` on the left-side, which will capture any row that did not meet any of the previous criteria. The right-side of this statement could be assigned a value like "check me!" or missing. @@ -1004,17 +1007,17 @@ linelist <- linelist %>% ``` -**fct_explicit_na()** +**fct_na_value_to_level()** This is a function from the **forcats** package. The **forcats** package handles columns of class Factor. Factors are R's way to handle *ordered* values such as `c("First", "Second", "Third")` or to set the order that values (e.g. hospitals) appear in tables and plots. See the page on [Factors](factors.qmd). If your data are class Factor and you try to convert `NA` to "Missing" by using `replace_na()`, you will get this error: `invalid factor level, NA generated`. You have tried to add "Missing" as a value, when it was not defined as a possible level of the factor, and it was rejected. -The easiest way to solve this is to use the **forcats** function `fct_explicit_na()` which converts a column to class factor, and converts `NA` values to the character "(Missing)". +The easiest way to solve this is to use the **forcats** function `fct_na_value_to_level()` which converts a column to class factor, and converts `NA` values to the character "(Missing)". ```{r, eval=F} linelist %>% - mutate(hospital = fct_explicit_na(hospital)) + mutate(hospital = fct_na_value_to_level(hospital)) ``` A slower alternative would be to add the factor level using `fct_expand()` and then convert the missing values. @@ -1048,9 +1051,9 @@ linelist <- linelist %>% Use the R package **matchmaker** and its function `match_df()` to clean a data frame with a *cleaning dictionary*. 1) Create a cleaning dictionary with 3 columns: - * A "from" column (the incorrect value) - * A "to" column (the correct value) - * A column specifying the column for the changes to be applied (or ".global" to apply to all columns) + * A "from" column (the incorrect value) + * A "to" column (the correct value) + * A column specifying the column for the changes to be applied (or ".global" to apply to all columns) Note: .global dictionary entries will be overridden by column-specific dictionary entries. @@ -1173,10 +1176,10 @@ linelist <- linelist_raw %>% Here we describe some special approaches for creating categories from numerical columns. Common examples include age categories, groups of lab values, etc. Here we will discuss: -* `age_categories()`, from the **epikit** package -* `cut()`, from **base** R -* `case_when()` -* quantile breaks with `quantile()` and `ntile()` +* `age_categories()`, from the **epikit** package +* `cut()`, from **base** R +* `case_when()` +* quantile breaks with `quantile()` and `ntile()` ### Review distribution {.unnumbered} @@ -1292,14 +1295,14 @@ See the function's Help page for more details (enter `?age_categories` in the R `cut()` is a **base** R alternative to `age_categories()`, but I think you will see why `age_categories()` was developed to simplify this process. Some notable differences from `age_categories()` are: -* You do not need to install/load another package +* You do not need to install/load another package * You can specify whether groups are open/closed on the right/left -* You must provide accurate labels yourself -* If you want 0 included in the lowest group you must specify this +* You must provide accurate labels yourself +* If you want 0 included in the lowest group you must specify this The basic syntax within `cut()` is to first provide the numeric column to be cut (`age_years`), and then the *breaks* argument, which is a numeric vector `c()` of break points. Using `cut()`, the resulting column is an ordered factor. -By default, the categorization occurs so that the right/upper side is "open" and inclusive (and the left/lower side is "closed" or exclusive). This is the opposite behavior from the `age_categories()` function. The default labels use the notation "(A, B]", which means A is not included but B is. **Reverse this behavior by providing the `right = TRUE` argument**. +By default, the categorization occurs so that the right/upper side is "open" and inclusive (and the left/lower side is "closed" or exclusive). This is the opposite behavior from the `age_categories()` function. The default labels use the notation "(A, B]", which means A is not included but B is.**Reverse this behavior by providing the `right = TRUE` argument**. Thus, by default, "0" values are excluded from the lowest group, and categorized as `NA`! "0" values could be infants coded as age 0 so be careful! To change this, add the argument `include.lowest = TRUE` so that any "0" values will be included in the lowest group. The automatically-generated label for the lowest category will then be "[A],B]". Note that if you include the `include.lowest = TRUE` argument **and** `right = TRUE`, the extreme inclusion will now apply to the *highest* break point value and category, not the lowest. @@ -1339,7 +1342,7 @@ table("Numeric Values" = linelist$age_years, # names specified in table for cl **Re-labeling `NA` values** -You may want to assign `NA` values a label such as "Missing". Because the new column is class Factor (restricted values), you cannot simply mutate it with `replace_na()`, as this value will be rejected. Instead, use `fct_explicit_na()` from **forcats** as explained in the [Factors](factors.qmd) page. +You may want to assign `NA` values a label such as "Missing". Because the new column is class Factor (restricted values), you cannot simply mutate it with `replace_na()`, as this value will be rejected. Instead, use `fct_na_value_to_level()` from **forcats** as explained in the [Factors](factors.qmd) page. ```{r} linelist <- linelist %>% @@ -1353,9 +1356,9 @@ linelist <- linelist %>% labels = c("0-4", "5-9", "10-14", "15-19", "20-29", "30-49", "50-69", "70-100")), # make missing values explicit - age_cat = fct_explicit_na( + age_cat = fct_na_value_to_level( age_cat, - na_level = "Missing age") # you can specify the label + level = "Missing age") # you can specify the label ) # table to view counts @@ -1667,10 +1670,15 @@ What other criteria can we filter on to remove the first outbreak (in 2012 & 201 We want to exclude: -* The ` nrow(linelist %>% filter(hospital %in% c("Hospital A", "Hospital B") | date_onset < as.Date("2013-06-01")))` rows with onset in 2012 and 2013 at either hospital A, B, or Port: - * Exclude ` nrow(linelist %>% filter(date_onset < as.Date("2013-06-01")))` rows with onset in 2012 and 2013 - * Exclude ` nrow(linelist %>% filter(hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))` rows from Hospitals A & B with missing onset dates - * Do **not** exclude ` nrow(linelist %>% filter(!hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))` other rows with missing onset dates. +* The rows with onset in 2012 and 2013 at either hospital A, B, or Port: +` nrow(linelist %>% filter(hospital %in% c("Hospital A", "Hospital B") | date_onset < as.Date("2013-06-01")))` + + * Exclude rows with onset in 2012 and 2013 + ` nrow(linelist %>% filter(date_onset < as.Date("2013-06-01")))` + * Exclude rows from Hospitals A & B with missing onset dates + ` nrow(linelist %>% filter(hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))` + * Do **not** exclude other rows with missing onset dates. + ` nrow(linelist %>% filter(!hospital %in% c('Hospital A', 'Hospital B') & is.na(date_onset)))` We start with a linelist of ` `nrow(linelist)`. Here is our filter statement: @@ -1845,7 +1853,7 @@ For example, this code applies `rowwise()` and then creates a new column that su ```{r,} linelist %>% rowwise() %>% - mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == "yes")) %>% + mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == "yes"), na.rm =T) %>% ungroup() %>% select(fever, chills, cough, aches, vomit, num_symptoms) # for display ``` @@ -1855,9 +1863,9 @@ As you specify the column to evaluate, you may want to use the "tidyselect" hel Put the column-specification criteria within the **dplyr** function `c_across()`. This is because `c_across` ([documentation](https://dplyr.tidyverse.org/reference/c_across.html)) is designed to work with `rowwise()` specifically. For example, the following code: -* Applies `rowwise()` so the following operation (`sum()`) is applied within each row (not summing entire columns) -* Creates new column `num_NA_dates`, defined for each row as the number of columns (with name containing "date") for which `is.na()` evaluated to TRUE (they are missing data). -* `ungroup()` to remove the effects of `rowwise()` for subsequent steps +* Applies `rowwise()` so the following operation (`sum()`) is applied within each row (not summing entire columns) +* Creates new column `num_NA_dates`, defined for each row as the number of columns (with name containing "date") for which `is.na()` evaluated to TRUE (they are missing data) +* `ungroup()` to remove the effects of `rowwise()` for subsequent steps ```{r,} linelist %>% diff --git a/new_pages/cleaning_files/figure-html/unnamed-chunk-69-1.png b/new_pages/cleaning_files/figure-html/unnamed-chunk-69-1.png new file mode 100644 index 00000000..e06a3121 Binary files /dev/null and b/new_pages/cleaning_files/figure-html/unnamed-chunk-69-1.png differ diff --git a/new_pages/cleaning_files/figure-html/unnamed-chunk-70-1.png b/new_pages/cleaning_files/figure-html/unnamed-chunk-70-1.png new file mode 100644 index 00000000..e06a3121 Binary files /dev/null and b/new_pages/cleaning_files/figure-html/unnamed-chunk-70-1.png differ diff --git a/new_pages/cleaning_files/figure-html/unnamed-chunk-87-1.png b/new_pages/cleaning_files/figure-html/unnamed-chunk-87-1.png new file mode 100644 index 00000000..cdfad7a8 Binary files /dev/null and b/new_pages/cleaning_files/figure-html/unnamed-chunk-87-1.png differ diff --git a/new_pages/cleaning_files/figure-html/unnamed-chunk-88-1.png b/new_pages/cleaning_files/figure-html/unnamed-chunk-88-1.png new file mode 100644 index 00000000..cdfad7a8 Binary files /dev/null and b/new_pages/cleaning_files/figure-html/unnamed-chunk-88-1.png differ diff --git a/new_pages/collaboration.qmd b/new_pages/collaboration.qmd index a5261477..59eba8f2 100644 --- a/new_pages/collaboration.qmd +++ b/new_pages/collaboration.qmd @@ -15,7 +15,7 @@ and most used options for version control. background routinely learn to use version control software (Git, Mercurial, Subversion or others), few of us from quantitative disciplines are taught these skills. Consequently, most epidemiologists never -hear of it during their studies, and have to learn it on the fly. +hear of it during their studies, and have to learn it on the job. **Wait, I heard of Github, is it the same?** - Not exactly, but you often use them together, and we will show you how to. In short: @@ -42,25 +42,25 @@ computer, and remotely on a **Github** server. Using **Git** facilitates: 1) Archiving documented versions with incremental changes so that you - can easily revert backwards to any previous state + can easily revert backwards to any previous state. 2) Having parallel *branches*, i.e. developing/"working" versions with - structured ways to integrate the changes after review + structured ways to integrate the changes after review. This can be done locally on your computer, even if you don't collaborate with other people. Have you ever: -- regretted having deleted a section of code, only to realize two +- Regretted having deleted a section of code, only to realize two months later that you actually needed it? -- come back on a project that had been on pause and attempted to +- Come back on a project that had been on pause and attempted to remember whether you had made that tricky modification in one of the models? -- had a *file model_1.R* and another file *model_1\_test.R* and a file +- Had a *file model_1.R* and another file *model_1\_test.R* and a file *model_1\_not_working.R* to try things out? -- had a file *report.Rmd*, a file *report_full.Rmd*, a file +- Had a file *report.Rmd*, a file *report_full.Rmd*, a file *report_true_final.Rmd*, a file *report_final_20210304.Rmd*, a file *report_final_20210402.Rmd* and cursed your archiving skills? @@ -71,18 +71,18 @@ However, it becomes even more powerful when used with a online repository such as Github to support **collaborative projects**. This facilitates: - Collaboration: others can review, comment on, and - accept/decline changes + accept/decline changes. - Sharing your code, data, and outputs, and invite feedback - from the public (or privately, with your team) + from the public (or privately, with your team). and avoids: - "Oops, I forgot to send the last version and now you need to - redo two days worth of work on this new file" + redo two days worth of work on this new file". - Mina, Henry and Oumar all worked at the same time on one script and - need to manually merge their changes + need to manually merge their changes. - Two people try to modify the same file on Dropbox and Sharepoint and this creates a synchronization error. @@ -125,7 +125,7 @@ this chapter. Intermediate (more powerfull, but more complex) options include Source Tree, Gitkracken, Smart Git and others. -Quick explanation on [Git clients](https:/happygitwithr.com/git-client.html#git-client). +Quick explanation on [Git clients](https://happygitwithr.com/git-client). *Note: since interfaces actually all use Git internally, you can try several of them, switch from one to another on a given project, use the console punctually @@ -139,7 +139,7 @@ Console) or the Git Bash terminal. ### Github account {.unnumbered} -Sign-up for a free account at [github.com](github.com). +Sign-up for a free account at [github.com](https://github.com/). You may be offered to set-up two-factor authentication with an app on your phone. Read more in the Github [help @@ -156,7 +156,7 @@ clone a project from Github. As when learning R, there is a bit of vocabulary to remember to understand Git. Here are the [basics to get you going](https://www.freecodecamp.org/news/an-introduction-to-git-for-absolute-beginners-86fa1d32ff71/) -/ [interactive tutorial](learngitbranching.js.org). In the next +/ [interactive tutorial](https://learngitbranching.js.org/). In the next sections, we will show how to use interfaces, but it is good to have the vocabulary and concepts in mind, to build your mental model, and as you'll need them when using interfaces anyway. @@ -257,8 +257,8 @@ Two general approaches to set-up are: - Create a new R Project from an existing or new Github repository - (*preferred for beginners*), or -- Create a Github repository for an existing R project + (*preferred for beginners*), or, +- Create a Github repository for an existing R project. ### Start-up files {.unnumbered} @@ -326,13 +326,13 @@ Rstudio and Github desktop. #### In Rstudio {.unnumbered} In RStudio, start a new R project by clicking *File \> New Project \> -Version Control \> Git* +Version Control \> Git*. - When prompted for the "Repository URL", paste the HTTPS URL from - Github\ -- Assign the R project a short, informative name\ -- Choose where the new R Project will be saved locally\ -- Check "Open in new session" and click "Create project" + Github.\ +- Assign the R project a short, informative name.\ +- Choose where the new R Project will be saved locally.\ +- Check "Open in new session" and click "Create project". You are now in a new, local, RStudio project that is a clone of the @@ -341,15 +341,15 @@ linked. #### In Github Desktop {.unnumbered} -- Click on *File \> Clone a repository* +- Click on *File \> Clone a repository*. -- Select the URL tab +- Select the URL tab. -- Paste the HTTPS URL from Github in the first box +- Paste the HTTPS URL from Github in the first box. -- Select the folder in which you want to have your local repository +- Select the folder in which you want to have your local repository. -- Click "CLONE" +- Click "CLONE". ```{r echo=F, out.width = '100%', out.height='100%', fig.align = "center"} knitr::include_graphics(here::here("images", "github_clone_desktop.png")) @@ -361,8 +361,8 @@ An alternative setup scenario is that you have an existing R project with content, and you want to create a Github repository for it. 1) Create a new, empty Github repository for the project (see - instructions above)\ -2) Clone this repository locally (see HTTPS instructions above)\ + instructions above).\ +2) Clone this repository locally (see HTTPS instructions above).\ 3) Copy all the content from your pre-existing R project (codes, data, etc.) into this new empty, local, repository (e.g. use copy and paste).\ 4) Open your new project in RStudio, and go to the Git pane. The new files should @@ -388,17 +388,17 @@ Please note the buttons circled in the image above, as they will be referenced later (from left to right): - Button to *commit* the saved file changes to the local - branch (this will open a new window) + branch (this will open a new window). - Blue arrow to *pull* (update your local version of the branch with - any changes made on the remote/Github version of that branch) + any changes made on the remote/Github version of that branch). - Green arrow to *push* (send any commits/changes for your local version of the branch to the remote/Github version of that branch) -- The Git tab in RStudio +- The Git tab in RStudio. - Button to create a NEW branch using whichever local branch is shown to the right as the base. *You almost always want to branch off from - the main branch (after you first pull to update the main branch)* -- The branch you are currently working in -- Changes you made to code or other files will appear below + the main branch (after you first pull to update the main branch)*. +- The branch you are currently working in. +- Changes you made to code or other files will appear below. #### In Github Desktop {-} @@ -428,20 +428,20 @@ easy and fast. A typical workflow is as follow: 1. Make sure that your local repository is up-to-date, update it if - not + not. 2. Go to the branch you were working on previously, or create a new - branch to try out some things + branch to try out some things. 3. Work on the files locally on your computer, make one or several - commits to this branch + commits to this branch. -4. Update the remote version of the branch with your changes (push) +4. Update the remote version of the branch with your changes (push). 5. When you are satisfied with your branch, you can merge the online version of the working branch into the online "main" branch to - transfer the changes + transfer the changes. Other team members may be doing the same thing with their own branches, or perhaps contributing commits into your working branch as well. @@ -464,7 +464,7 @@ knitr::include_graphics(here::here("images", "GitHub-Flow.png")) ``` Image -[source](https://build5nines.com/introduction-to-git-version-control-workflow/) +[source](https://build5nines.com/introduction-to-git-version-control-workflow/). ## Create a new branch @@ -549,7 +549,7 @@ knitr::include_graphics(here::here("images", "github_tracking2.png")) You might be wondering what the yellow, blue, green, and red squares next to the file names represent. Here is a snapshot from the [RStudio -cheatsheet](https://www.rstudio.com/wp-content/uploads/2016/01/rstudio-IDE-cheatsheet.pdf) +cheatsheet](https://raw.githubusercontent.com/rstudio/cheatsheets/refs/heads/main/pngs/rstudio-ide.png) that explains their meaning. Note that changes with yellow "?" can still be staged, committed, and pushed. @@ -558,18 +558,18 @@ knitr::include_graphics(here::here("images", "github_tracking.png")) ``` - Press the "Commit" button in the Git tab, which will open a new - window (shown below) + window (shown below). -- Click on a file name in the upper-left box +- Click on a file name in the upper-left box. - Review the changes you made to that file (highlighted below in green - or red) + or red). - "Stage" the file, which will include those changes in the commit. Do this by checking the box next to the file name. Alternatively, you - can highlight multiple file names and then click "Stage" + can highlight multiple file names and then click "Stage". -- Write a commit message that is short but descriptive (required) +- Write a commit message that is short but descriptive (required). - Press the "Commit" button. A pop-up box will appear showing success or an error message. @@ -671,7 +671,7 @@ you were working on. *PULL* - First, click the "Pull" icon (downward arrow) which fetches and pulls at the same time. -*PUSH* - Clicking the green "Pull" icon (upward arrow). You may be asked +*PUSH* - Clicking the green "Push" icon (upward arrow). You may be asked to enter your Github username and password. The first time you are asked, you may need to enter two Git command lines into the *Terminal*: @@ -685,9 +685,9 @@ on Git commands. ***TIP:*** Asked to provide your password too often? See these chapters 10 & 11 of this -[tutorial](https://happygitwithr.com/credential-caching.html#credential-caching) +[tutorial](https://happygitwithr.com/ssh-keys) to connect to a repository using a SSH key (more -complicated) +complicated) . #### In Github Desktop {.unnumbered} @@ -817,9 +817,9 @@ github](https://github.com/tidyverse/dplyr/pulls). You can submit a pull request (PR) directly form the website (as illustrated bellow) or from Github Desktop. -- Go to Github repository (online) -- View the tab "Pull Requests" and click the "New pull request" button -- Select from the drop-down menu to merge your branch into main +- Go to Github repository (online). +- View the tab "Pull Requests" and click the "New pull request" button. +- Select from the drop-down menu to merge your branch into main. - Write a detailed Pull Request comment and click "Create Pull Request". @@ -906,12 +906,12 @@ a branch Be sure to also delete the branch locally on your computer. This will not happen automatically. -- From RStudio, make sure you are in the Main branch +- From RStudio, make sure you are in the Main branch. - Switch to typing Git commands in the RStudio "Terminal" (the tab adjacent to the R console), and type: **git branch -d branch_name**, where "branch_name" is the name of your branch to be - deleted -- Refresh your Git tab and the branch should be gone + deleted. +- Refresh your Git tab and the branch should be gone. #### In Github Desktop @@ -1059,7 +1059,7 @@ website](https://learngitbranching.js.org/). You enter commands in a Git shell. -*Option 1* You can open a new Terminal in RStudio. This tab is next to +*Option 1*: You can open a new Terminal in RStudio. This tab is next to the R Console. If you cannot type any text in it, click on the drop-down menu below "Terminal" and select "New terminal". Type the commands at the blinking space in front of the dollar sign "\$". @@ -1068,12 +1068,12 @@ commands at the blinking space in front of the dollar sign "\$". knitr::include_graphics(here::here("images", "github_terminal.png")) ``` -*Option 2* You can also open a *shell* (a terminal to enter commands) by +*Option 2*: You can also open a *shell* (a terminal to enter commands) by clicking the blue "gears" icon in the Git tab (near the RStudio Environment). Select "Shell" from the drop-down menu. A new window will open where you can type the commands after the dollar sign "\$". -*Option 3* Right click to open "Git Bash here" which will open the same +*Option 3*: Right click to open "Git Bash here" which will open the same sort of terminal, or open *Git Bash* form your application list. [More beginner-friendly informations on Git Bash](https://happygitwithr.com/shell.html), how to find it and some bash commands you will need. @@ -1118,32 +1118,33 @@ The [Github.com documentation and start guide](https://docs.github.com/en/github). The RStudio ["IDE" -cheatsheet](https://www.rstudio.com/wp-content/uploads/2016/01/rstudio-IDE-cheatsheet.pdf) +cheatsheet](https://rstudio.github.io/cheatsheets/html/rstudio-ide.html) which includes tips on Git with RStudio. - +[GitHub: A beginner's guide to going back in time (aka fixing mistakes)](https://ohi-science.org/news/github-going-back-in-time) **Git commands for beginners** An [interactive -tutorial](learngitbranching.js.org) to learn +tutorial](https://learngitbranching.js.org/) to learn Git commands. -: +[Git for Absolute Beginners](https://www.freecodecamp.org/news/an-introduction-to-git-for-absolute-beginners-86fa1d32ff71) good for learning the absolute basics to track changes in one folder on you own computer. -Nice schematics to understand branches: - +Nice schematics to [understand branches](https://speakerdeck.com/alicebartlett/git-for-humans) **Tutorials covering both basic and more advanced subjects** - +[Learn Git in 30 Minutes](https://tutorialzine.com/2016/06/learn-git-in-30-minutes) - - (short course) - +[Commands and Operations in Git](https://dzone.com/articles/git-tutorial-commands-and-operations-in-git) + +[Version Control with Git (short course)](https://swcarpentry.github.io/git-novice/) + +[Introduction to Git (book)](https://rsjakob.gitbooks.io/git/content/chapter1.html) The [Pro Git book](https://git-scm.com/book/en/v2) is considered an official reference. While some chapters are ok, it is usually a bit _technical_. It is probably a good resource diff --git a/new_pages/combination_analysis.qmd b/new_pages/combination_analysis.qmd index 78f4bd70..60c393fe 100644 --- a/new_pages/combination_analysis.qmd +++ b/new_pages/combination_analysis.qmd @@ -49,9 +49,9 @@ This analysis plots the frequency of different **combinations** of values/respon This analysis is also often called: -* **"Multiple response analysis"** -* **"Sets analysis"** -* **"Combinations analysis"** +* **"Multiple response analysis"** +* **"Sets analysis"** +* **"Combinations analysis"** In the example plot above, five symptoms are shown. Below each vertical bar is a line and dots indicating the combination of symptoms reflected by the bar above. To the right, horizontal bars reflect the frequency of each individual symptom. @@ -73,9 +73,12 @@ This code chunk shows the loading of packages required for the analyses. In this ```{r, warning=F, message=F} pacman::p_load( + rio, # for importing data + here, # for relative filepaths tidyverse, # data management and visualization UpSetR, # special package for combination plots - ggupset) # special package for combination plots + ggupset # special package for combination plots + ) ``` @@ -86,7 +89,7 @@ To begin, we import the cleaned linelist of cases from a simulated Ebola epidemi -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist_sym <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -127,10 +130,10 @@ mutate(fever = ifelse(fever == "yes", "fever", NA), Now we make two final columns: -1. Concatenating (gluing together) all the symptoms of the patient (a character column) -2. Convert the above column to class *list*, so it can be accepted by **ggupset** to make the plot +1. Concatenating (gluing together) all the symptoms of the patient (a character column). +2. Convert the above column to class *list*, so it can be accepted by **ggupset** to make the plot. -See the page on [Characters and strings](characters_strings.qmd) to learn more about the `unite()` function from **stringr** +See the page on [Characters and strings](characters_strings.qmd) to learn more about the `unite()` function from **stringr**. ```{r, warning=F, message=F} linelist_sym_1 <- linelist_sym_1 %>% @@ -145,7 +148,7 @@ linelist_sym_1 <- linelist_sym_1 %>% ) ``` -View the new data. Note the two columns towards the right end - the pasted combined values, and the list +View the new data. Note the two columns towards the right end - the pasted combined values, and the list. ```{r, echo=F, , warning=F, message=F} DT::datatable(head(linelist_sym_1,50), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap') @@ -172,7 +175,7 @@ geom_bar() + scale_x_upset( reverse = FALSE, n_intersections = 10, - sets = c("fever", "chills", "cough", "aches", "vomit"))+ + sets = c("fever", "chills", "cough", "aches", "vomit")) + labs( title = "Signs & symptoms", subtitle = "10 most frequent combinations of signs and symptoms", @@ -239,7 +242,8 @@ linelist_sym_2 %>% point.size = 3.5, line.size = 2, mainbar.y.label = "Symptoms Combinations", - sets.x.label = "Patients with Symptom") + sets.x.label = "Patients with Symptom" + ) ``` diff --git a/new_pages/contact_tracing.qmd b/new_pages/contact_tracing.qmd index a6dadee9..4ed1c1ec 100644 --- a/new_pages/contact_tracing.qmd +++ b/new_pages/contact_tracing.qmd @@ -1,6 +1,6 @@ # Contact tracing { } - +\n This page demonstrates descriptive analysis of contact tracing data, addessing some key considerations and approaches unique to these kinds of data. @@ -8,7 +8,7 @@ This page references many of the core R data management and visualisation compet For demonstration purposes we will use sample contact tracing data from the [Go.Data](https://www.who.int/tools/godata) platform. The principles covered here will apply for contact tracing data from other platforms - you may just need to undergo different data pre-processing steps depending on the structure of your data. -You can read more about the Go.Data project on the [Github Documentation site](https://worldhealthorganization.github.io/godata/) or [Community of Practice](https://community-godata.who.int/). +You can read more about the Go.Data project on the [Github Documentation site](https://worldhealthorganization.github.io/godata/). ## Preparation @@ -17,7 +17,7 @@ You can read more about the Go.Data project on the [Github Documentation site](h This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. -```{r, message = F} +```{r, message = F, warning=F} pacman::p_load( rio, # importing data here, # relative file pathways @@ -69,7 +69,7 @@ Below, the datasets are imported using the `import()` function from the **rio** These data are a table of the cases, and information about them. -```{r} +```{r, warning=F, message=F} cases <- import(here("data", "godata", "cases_clean.rds")) %>% select(case_id, firstName, lastName, gender, age, age_class, occupation, classification, was_contact, hospitalization_typeid) @@ -85,12 +85,12 @@ DT::datatable(cases, rownames = FALSE, options = list(pageLength = 5, scrollX=T) These data are a table of all the contacts and information about them. Again, provide your own file path. After importing we perform a few preliminary data cleaning steps including: -* Set age_class as a factor and reverse the level order so that younger ages are first -* Select only certain column, while re-naming a one of them -* Artificially assign rows with missing admin level 2 to "Djembe", to improve clarity of some example visualisations +* Set age_class as a factor and reverse the level order so that younger ages are first. +* Select only certain column, while re-naming a one of them. +* Artificially assign rows with missing admin level 2 to "Djembe", to improve clarity of some example visualisations. -```{r} +```{r, warning=F, message=F} contacts <- import(here("data", "godata", "contacts_clean.rds")) %>% mutate(age_class = forcats::fct_rev(age_class)) %>% select(contact_id, contact_status, firstName, lastName, gender, age, @@ -112,7 +112,7 @@ These data are records of the "follow-up" interactions with the contacts. Each c We import and perform a few cleaning steps. We select certain columns, and also convert a character column to all lowercase values. -```{r} +```{r, warning=F, message=F} followups <- rio::import(here::here("data", "godata", "followups_clean.rds")) %>% select(contact_id, followup_status, followup_number, date_of_followup, admin_2_name, admin_1_name) %>% @@ -170,7 +170,7 @@ apyramid::age_pyramid( split_by = "gender") + # gender for halfs of pyramid labs( fill = "Gender", # title of legend - title = "Age/Sex Pyramid of COVID-19 contacts")+ # title of the plot + title = "Age/Sex Pyramid of COVID-19 contacts") + # title of the plot theme_minimal() # simple background ``` @@ -200,10 +200,10 @@ apyramid::age_pyramid( split_by = "category") + # by cases and contacts scale_fill_manual( values = c("orange", "purple"), # to specify colors AND labels - labels = c("Case", "Contact"))+ + labels = c("Case", "Contact")) + labs( fill = "Legend", # title of legend - title = "Age/Sex Pyramid of COVID-19 contacts and cases")+ # title of the plot + title = "Age/Sex Pyramid of COVID-19 contacts and cases") + # title of the plot theme_minimal() # simple background ``` @@ -217,12 +217,12 @@ occ_plot_data <- cases %>% count(occupation) # get counts by occupation # Make pie chart -ggplot(data = occ_plot_data, mapping = aes(x = "", y = n, fill = occupation))+ +ggplot(data = occ_plot_data, mapping = aes(x = "", y = n, fill = occupation)) + geom_bar(width = 1, stat = "identity") + coord_polar("y", start = 0) + labs( fill = "Occupation", - title = "Known occupations of COVID-19 cases")+ + title = "Known occupations of COVID-19 cases") + theme_minimal() + theme(axis.line = element_blank(), axis.title = element_blank(), @@ -250,10 +250,10 @@ contacts_per_case We use `geom_histogram()` to plot these data as a histogram. ```{r, warning=F, message=F} -ggplot(data = contacts_per_case)+ # begin with count data frame created above - geom_histogram(mapping = aes(x = n))+ # print histogram of number of contacts per case - scale_y_continuous(expand = c(0,0))+ # remove excess space below 0 on y-axis - theme_light()+ # simplify background +ggplot(data = contacts_per_case) + # begin with count data frame created above + geom_histogram(mapping = aes(x = n)) + # print histogram of number of contacts per case + scale_y_continuous(expand = c(0,0)) + # remove excess space below 0 on y-axis + theme_light() + # simplify background labs( title = "Number of contacts per case", y = "Cases", @@ -295,7 +295,7 @@ followups %>% filter(n > 1) # view records where count is more than 1 ``` -In our example data, the only records that this applies to are ones missing an ID! We can remove those. But, for purposes of demonstration we will go show the steps for de-duplication so there is only one follow-up encoutner per person per day. See the page on [De-duplication](deduplication.qmd) for more detail. We will assume that the most recent encounter record is the correct one. We also take the opportunity to clean the `followup_number` column (the "day" of follow-up which should range 1 - 14). +In our example data, the only records that this applies to are ones missing an ID! We can remove those. But, for purposes of demonstration we will go show the steps for de-duplication so there is only one follow-up encounter per person per day. See the page on [De-duplication](deduplication.qmd) for more detail. We will assume that the most recent encounter record is the correct one. We also take the opportunity to clean the `followup_number` column (the "day" of follow-up which should range 1 - 14). ```{r, warning=F, message=F} followups_clean <- followups %>% @@ -328,9 +328,9 @@ As the dates data are continuous, we will use a histogram to plot them with `dat We can see that the contacts were identified in waves (presumably corresponding with epidemic waves of cases), and that follow-up completion did not seemingly improve over the course of the epidemic. ```{r, warning=F, message=F} -ggplot(data = followups_clean)+ +ggplot(data = followups_clean) + geom_histogram(mapping = aes(x = date_of_followup, fill = followup_status)) + - scale_fill_discrete(drop = FALSE)+ # show all factor levels (followup_status) in the legend, even those not used + scale_fill_discrete(drop = FALSE) + # show all factor levels (followup_status) in the legend, even those not used theme_classic() + labs( x = "", @@ -352,11 +352,11 @@ If your outbreak is small enough, you may want to look at each contact individua A convenient visualisation mechanism (if the number of cases is not too large) can be a heat plot, made with `geom_tile()`. See more details in the [heat plot](heatmaps.qmd) page. ```{r, warning=F, message=F} -ggplot(data = followups_clean)+ +ggplot(data = followups_clean) + geom_tile(mapping = aes(x = followup_number, y = contact_id, fill = followup_status), - color = "grey")+ # grey gridlines - scale_fill_manual( values = c("yellow", "grey", "orange", "darkred", "darkgreen"))+ - theme_minimal()+ + color = "grey") + # grey gridlines + scale_fill_manual( values = c("yellow", "grey", "orange", "darkred", "darkgreen")) + + theme_minimal() + scale_x_continuous(breaks = seq(from = 1, to = 14, by = 1)) ``` @@ -375,22 +375,22 @@ plot_by_region <- followups_clean %>% # b mapping = aes(x = reorder(admin_2_name, n), # reorder admin factor levels by the numeric values in column 'n' y = n, # heights of bar from column 'n' fill = followup_status, # color stacked bars by their status - label = n))+ # to pass to geom_label() - geom_col()+ # stacked bars, mapping inherited from above + label = n)) + # to pass to geom_label() + geom_col() + # stacked bars, mapping inherited from above geom_text( # add text, mapping inherited from above size = 3, position = position_stack(vjust = 0.5), color = "white", check_overlap = TRUE, - fontface = "bold")+ - coord_flip()+ + fontface = "bold") + + coord_flip() + labs( x = "", y = "Number of contacts", title = "Contact Followup Status, by Region", fill = "Followup Status", subtitle = str_glue("Data as of {max(followups_clean$date_of_followup, na.rm=T)}")) + - theme_classic()+ # Simplify background + theme_classic() + # Simplify background facet_wrap(~admin_1_name, strip.position = "right", scales = "free_y", ncol = 1) # introduce facets plot_by_region @@ -439,12 +439,12 @@ table_data <- followups_clean %>% Now, based on our data structure, we will do the following: 1) Begin with the `followups` data and summarise it to contain, for each unique contact: - * The date of latest record (no matter the status of the encounter) - * The date of latest encounter where the contact was "seen" - * The encounter status at that final "seen" encounter (e.g. with symptoms, without symptoms) -2) Join these data to the contacts data, which contains other information such as the overall contact status, date of last exposure to a case, etc. Also we will calculate metrics of interest for each contact such as days since last exposure -3) We group the enhanced contact data by geographic region (`admin_2_name`) and calculate summary statistics per region -4) Finally, we format the table nicely for presentation + * The date of latest record (no matter the status of the encounter). + * The date of latest encounter where the contact was "seen". + * The encounter status at that final "seen" encounter (e.g. with symptoms, without symptoms). +2) Join these data to the contacts data, which contains other information such as the overall contact status, date of last exposure to a case, etc. Also we will calculate metrics of interest for each contact such as days since last exposure. +3) We group the enhanced contact data by geographic region (`admin_2_name`) and calculate summary statistics per region. +4) Finally, we format the table nicely for presentation. First we summarise the follow-up data to get the information of interest: @@ -586,16 +586,16 @@ and create a heat-map for age. ```{r, warning=F, message=F} -ggplot(data = long_prop)+ # use long data, with proportions as Freq +ggplot(data = long_prop) + # use long data, with proportions as Freq geom_tile( # visualize it in tiles aes( x = target_cases, # x-axis is case age y = source_cases, # y-axis is infector age - fill = Freq))+ # color of the tile is the Freq column in the data + fill = Freq)) + # color of the tile is the Freq column in the data scale_fill_gradient( # adjust the fill color of the tiles low = "blue", - high = "orange")+ - theme(axis.text.x = element_text(angle = 90))+ + high = "orange") + + theme(axis.text.x = element_text(angle = 90)) + labs( # labels x = "Target case age", y = "Source case age", @@ -609,8 +609,6 @@ ggplot(data = long_prop)+ # use long data, with proportions as Freq ## Resources -https://github.com/WorldHealthOrganization/godata/tree/master/analytics/r-reporting - -https://worldhealthorganization.github.io/godata/ +[Go.Data](https://worldhealthorganization.github.io/godata/) -https://community-godata.who.int/ +[Automated R Reporting using Go.Data API](https://github.com/WorldHealthOrganization/godata/tree/master/analytics/r-reporting) diff --git a/new_pages/data_table.qmd b/new_pages/data_table.qmd index 5e882b78..83b36a79 100644 --- a/new_pages/data_table.qmd +++ b/new_pages/data_table.qmd @@ -8,7 +8,7 @@ The handbook focusses on the **dplyr** “verb” functions and the **magrittr** ## Intro to data tables { } -A data table is a 2-dimensional data structure like a data frame that allows complex grouping operations to be performed. The data.table syntax is structured so that operations can be performed on rows, columns and groups. +A data table is a 2-dimensional data structure like a data frame that allows complex grouping operations to be performed. The **data.table** syntax is structured so that operations can be performed on rows, columns and groups. The structure is **DT[i, j, by]**, separated by 3 parts; the **i, j** and **by** arguments. The **i** argument allows for subsetting of required rows, the **j** argument allows you to operate on columns and the **by** argument allows you operate on columns by groups. @@ -45,8 +45,9 @@ This page will explore some of the core functions of **data.table** using the ca We import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow step-by-step, see instructions in the [Download book and data](data_used.qmd) page. The dataset is imported using the `import()` function from the **rio** package. See the page on [Import and export](importing.qmd) for various ways to import data. From here we use `data.table()` to convert the data frame to a data table. -```{r} -linelist <- rio::import(here("data", "linelist_cleaned.xlsx")) %>% data.table() +```{r, warning=F, message=F} +linelist <- rio::import(here("data", "linelist_cleaned.xlsx")) %>% + data.table() ``` The `fread()` function is used to directly import regular delimited files, such as .csv files, directly to a data table format. This function, and its counterpart, `fwrite()`, used for writing data.tables as regular delimited files are very fast and computationally efficient options for large databases. @@ -90,12 +91,13 @@ linelist[15:.N] #returns the 15th to the last row ### Using helper functions for filtering {.unnumbered} -Data table uses helper functions that make subsetting rows easy. The `%like%` function is used to match a pattern in a column, `%chin%` is used to match a specific character, and the `%between%` helper function is used to match numeric columns within a prespecified range. +Data table uses helper functions that make subsetting rows easy. The `%like%` function is used to match a pattern in a column, `%chin%` is used to match a specific character, and the `%between%` helper function is used to match numeric columns within a specified range. In the following examples we: -* filter rows where the hospital variable contains “Hospital” -* filter rows where the outcome is “Recover” or “Death” -* filter rows in the age range 40-60 + +* Filter rows where the hospital variable contains “Hospital” +* Filter rows where the outcome is “Recover” or “Death” +* Filter rows in the age range 40-60 ```{r, eval=F} linelist[hospital %like% "Hospital"] #filter rows where the hospital variable contains “Hospital” @@ -149,6 +151,7 @@ Remember using the .() wrap in the j argument facilitates computation, returns a The **by** argument is the third argument in the **DT[i, j, by]** structure. The **by** argument accepts both a character vector and the `list()` or `.()` syntax. Using the `.()` syntax in the **by** argument allows column renaming on the fly. In the following examples we: + * group the number of cases by hospital * in cases 18 years old or over, calculate the mean height and weight of cases according to gender and whether they recovered or died * in admissions that lasted over 7 days, count the number of cases according to the month they were admitted and the hospital they were admitted to @@ -215,14 +218,12 @@ Further complex aggregations are beyond the scope of this introductory chapter, ## Resources { } Here are some useful resources for more information: -* https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html -* https://github.com/Rdatatable/data.table -* https://s3.amazonaws.com/assets.datacamp.com/img/blog/data+table+cheat+sheet.pdf -* https://www.machinelearningplus.com/data-manipulation/datatable-in-r-complete-guide/ -* https://www.datacamp.com/community/tutorials/data-table-r-tutorial - -You can perform any summary function on grouped data; see the Cheat Sheet here for more info: -https://s3.amazonaws.com/assets.datacamp.com/blog_assets/datatable_Cheat_Sheet_R.pdf + +* [data.table vignette](https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html) +* [data.table github](https://github.com/Rdatatable/data.table) +* [data.table cheatsheet](https://s3.amazonaws.com/assets.datacamp.com/img/blog/data+table+cheat+sheet.pdf) +* [Guide to data.table](https://www.machinelearningplus.com/data-manipulation/datatable-in-r-complete-guide/) +* [data.table tutorial](https://www.datacamp.com/community/tutorials/data-table-r-tutorial) diff --git a/new_pages/data_used.de.qmd b/new_pages/data_used.de.qmd index d8d1c43c..8faabd71 100644 --- a/new_pages/data_used.de.qmd +++ b/new_pages/data_used.de.qmd @@ -15,7 +15,7 @@ Du kannst die Offline-Version dieses Handbuchs als HTML-Datei herunterladen, so ### Verwende den Download-Link {.unnumbered} -Für schnellen Zugriff, **Rechtsklick auf** [diesen Link](https://github.com/appliedepi/epiRhandbook_eng/raw/master/offline_long/index.de.html) **und wähle "Link speichern unter"**. +Für schnellen Zugriff, **Rechtsklick auf** [diesen Link](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/Epi_R_Handbook_offline.html) **und wähle "Link speichern unter"**. Auf einem Mac klickst du mit Cmd+Klick. Wenn du ein Handy hast, halte den Link gedrückt und wähle "Link speichern". Das Handbuch wird auf dein Gerät heruntergeladen. Wenn ein Bildschirm mit unbearbeitetem HTML-Code erscheint, vergewissere dich, dass du die obigen Anweisungen befolgt hast, oder versuche Option 2. diff --git a/new_pages/data_used.es.qmd b/new_pages/data_used.es.qmd index ddf845b8..103962c9 100644 --- a/new_pages/data_used.es.qmd +++ b/new_pages/data_used.es.qmd @@ -20,7 +20,7 @@ Puedes descargar la versión sin conexión de este manual. Éste es un archivo H ### Utilizando el enlace de descarga {.unnumbered} -Para acceder rápidamente, **clica con el botón derecho** [en este enlace](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/index.es.html) **y selecciona "Guardar enlace como"**. +Para acceder rápidamente, **clica con el botón derecho** [en este enlace](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/Epi_R_Handbook_offline.html) **y selecciona "Guardar enlace como"**. Si es un Mac, utiliza Cmd+clic. Si es un móvil, mantén clicado el enlace y selecciona "Guardar enlace". El manual se descargará en el dispositivo. Si aparece una pantalla con código HTML sin procesar, asegúrate de haber seguido las instrucciones anteriores o prueba la opción 2. diff --git a/new_pages/data_used.fr.qmd b/new_pages/data_used.fr.qmd index 34cee5cb..176fe162 100644 --- a/new_pages/data_used.fr.qmd +++ b/new_pages/data_used.fr.qmd @@ -21,7 +21,7 @@ Vous pouvez télécharger la version hors-ligne de ce manuel en tant que fichier ### Utiliser le lien de téléchargement {.unnumbered} -Pour un accès rapide, **cliquez à droite** [ce lien](https://github.com/appliedepi/epiRhandbook_fr/raw/main/offline_long/index.fr.html) **et sélectionnez "Enregistrer le lien sous"**. +Pour un accès rapide, **cliquez à droite** [ce lien](https://github.com/appliedepi/epiRhandbook_fr/raw/main/offline_long/Epi_R_Handbook_offline.html) **et sélectionnez "Enregistrer le lien sous"**. Si vous êtes sur un Mac, utilisez Cmd + clic. Si vous êtes sur un téléphone portable, appuyez sur le lien et maintenez-le enfoncé, puis sélectionnez "Enregistrer le lien". Le manuel sera téléchargé sur votre appareil. Si un écran contenant un code HTML brut apparaît, assurez-vous d'avoir suivi les instructions ci-dessus ou essayez l'option 2. diff --git a/new_pages/data_used.html b/new_pages/data_used.html new file mode 100644 index 00000000..8cd7cc5a --- /dev/null +++ b/new_pages/data_used.html @@ -0,0 +1,1623 @@ + + + + + + + + + +2  Download handbook and data – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    2  Download handbook and data

    +
    + + + +
    + + + + +
    + + + +
    + + + +
    +

    2.1 Download offline handbook

    +

    You can download the offline version of this handbook as an HTML file so that you can view the file in your web browser even if you no longer have internet access. If you are considering offline use of the Epi R Handbook here are a few things to consider:

    +
      +
    • When you open the file it may take a minute or two for the images and the ‘Table of Contents’ to load.
      +
    • +
    • The offline handbook has a slightly different layout - one very long page with Table of Contents on the left. To search for specific terms use Ctrl+f (Cmd-f).
      +
    • +
    • See the Suggested packages page to assist you with installing appropriate R packages before you lose internet connectivity. You will not be able to install new packages without internet access.
      +
    • +
    • Install our R package epirhandbook that contains all the example data (install process described below).
    • +
    +

    There are two ways you can download the handbook:

    + +
    +

    Option 2: Use our R package

    +

    We offer an R package called epirhandbook. It includes a function download_book() that downloads the handbook file from our Github repository to your computer. If you wish to download the book in a language other than English, you can specify this by specifying the language.

    +
    +
    download_book(fr)
    +
    +

    Currently we support French (fr), German (de), Spanish (es), Portuguese (pt), Vietnamese (vn), Japanese (jp), Turkish (tr), and Russian (ru).

    +

    This package also contains a function get_data() that downloads all the example data to your computer.

    +

    Run the following code to install our R package epirhandbook from the Github repository appliedepi. This package is not on CRAN, so use the special function p_install_gh() to install it from Github.

    +
    +
    # install the latest version of the Epi R Handbook package
    +pacman::p_install_gh("appliedepi/epirhandbook")
    +
    +

    Now, load the package for use in your current R session:

    +
    +
    # load the package for use
    +pacman::p_load(epirhandbook)
    +
    +

    Next, run the package’s function download_book() (with empty parentheses) to download the handbook to your computer. Assuming you are in RStudio, a window will appear allowing you to select a save location.

    +
    +
    # download the offline handbook to your computer
    +download_book()
    +
    +
    +
    +
    +

    2.2 Download data to follow along

    +

    To “follow along” with the handbook pages, you can download the example data and outputs.

    +
    +

    Use our R package

    +

    The easiest approach to download all the data is to install our R package epirhandbook. It contains a function get_data() that saves all the example data to a folder of your choice on your computer.

    +

    To install our R package epirhandbook, run the following code. This package is not on CRAN, so use the function p_install_gh() to install it. The input is referencing our Github organisation (“appliedepi”) and the epirhandbook package.

    +
    +
    # install the latest version of the Epi R Handbook package
    +pacman::p_install_gh("appliedepi/epirhandbook")
    +
    +

    Now, load the package for use in your current R session:

    +
    +
    # load the package for use
    +pacman::p_load(epirhandbook)
    +
    +

    Next, use the package’s function get_data() to download the example data to your computer. Run get_data("all") to get all the example data, or provide a specific file name and extension within the quotes to retrieve only one file.

    +

    The data have already been downloaded with the package, and simply need to be transferred out to a folder on your computer. A pop-up window will appear, allowing you to select a save folder location. We suggest you create a new “data” folder as there are almost 80 files (including example data and example outputs).

    +
    +
    # download all the example data into a folder on your computer
    +get_data("all")
    +
    +# download only the linelist example data into a folder on your computer
    +get_data(file = "linelist_cleaned.rds")
    +
    +
    +
    # download a specific file into a folder on your computer
    +get_data("linelist_cleaned.rds")
    +
    +

    Once you have used get_data() to save a file to your computer, you will still need to import it into R. see the Import and export page for details.

    +

    If you wish, you can review all the data used in this handbook in the “data” folder of our Github repository.

    +
    +
    +

    Download one-by-one

    +

    This option involves downloading the data file-by-file from our Github repository via either a link or an R command specific to the file. Some file types allow a download button, while others can be downloaded via an R command.

    +
    +

    Case linelist

    +

    This is a fictional Ebola outbreak, expanded by the handbook team from the ebola_sim practice dataset in the outbreaks package.

    + +

    Other related files:

    + +
    +
    pacman::p_load(rio) # install/load the rio package
    +
    +# import the file directly from Github
    +cleaning_dict <- import("https://github.com/appliedepi/epirhandbook_eng/raw/master/data/case_linelists/cleaning_dict.csv")
    +
    +
    +
    +

    Malaria count data

    +

    These data are fictional counts of malaria cases by age group, facility, and day. A .rds file is an R-specific file type that preserves column classes. This ensures you will have only minimal cleaning to do after importing the data into R.

    +

    Click to download the malaria count data (.rds file)

    +
    +
    +

    Likert-scale data

    +

    These are fictional data from a Likert-style survey, used in the page on Demographic pyramids and Likert-scales. You can load these data directly into R by running the following commands:

    +
    +
    pacman::p_load(rio) # install/load the rio package
    +
    +# import the file directly from Github
    +likert_data <- import("https://raw.githubusercontent.com/appliedepi/epirhandbook_eng/master/data/likert_data.csv")
    +
    +
    +
    +

    Flexdashboard

    +

    Below are links to the file associated with the page on Dashboards with R Markdown:

    +
      +
    • To download the R Markdown for the outbreak dashboard, right-click this link (Cmd+click for Mac) and select “Save link as”.
      +
    • +
    • To download the HTML dashboard, right-click this link (Cmd+click for Mac) and select “Save link as”.
    • +
    +
    +
    +

    Contact Tracing

    +

    The Contact Tracing page demonstrated analysis of contact tracing data, using example data from Go.Data. The data used in the page can be downloaded as .rds files by clicking the following links:

    +

    Click to download the case investigation data (.rds file)

    +

    Click to download the contact registration data (.rds file)

    +

    Click to download the contact follow-up data (.rds file)

    +

    Click to download the contact follow-up data (.rds file)

    +

    NOTE: Structured contact tracing data from other software (e.g. KoBo, DHIS2 Tracker, CommCare) may look different. If you would like to contribute alternative sample data or content for this page, please contact us.

    +

    TIP: If you are deploying Go.Data and want to connect to your instance’s API, see the Import and export page (API section) and the Go.Data Community of Practice.

    +
    +
    +

    GIS

    +

    Shapefiles have many sub-component files, each with a different file extention. One file will have the “.shp” extension, but others may have “.dbf”, “.prj”, etc. You will need to have all of these different files within the same folder to use the shapefile.

    +

    The GIS basics page provides links to the Humanitarian Data Exchange website where you can download the shapefiles directly as zipped files.

    +

    For example, the health facility points data can be downloaded here. Download “hotosm_sierra_leone_health_facilities_points_shp.zip”. Once saved to your computer, “unzip” the folder. You will see several files with different extensions (e.g. “.shp”, “.prj”, “.shx”) - all these must be saved to the same folder on your computer. Then to import into R, provide the file path and name of the “.shp” file to st_read() from the sf package (as described in the GIS basics page).

    +

    If you follow Option 1 to download all the example data (via our R package epirhandbook), all the shapefiles are included.

    +

    Alternatively, you can download the shapefiles from the R Handbook Github “data” folder (see the “gis” sub-folder). However, be aware that you will need to download each sub-file individually to your computer. In Github, click on each file individually and download them by clicking on the “Download” button. Below, you can see how the shapefile “sle_adm3” consists of many files - each of which would need to be downloaded from Github.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    Phylogenetic trees

    +

    See the page on Phylogenetic trees. Newick file of phylogenetic tree constructed from whole genome sequencing of 299 Shigella sonnei samples and corresponding sample data (converted to a text file). The Belgian samples and resulting data are kindly provided by the Belgian NRC for Salmonella and Shigella in the scope of a project conducted by an ECDC EUPHEM Fellow, and are published here with the sequences found here. The international data are openly available on public databases (National Center for Biotechnology Information, NCBI) and have been previously published.

    +
      +
    • To download the “Shigella_tree.txt” phylogenetic tree file, right-click this link (Cmd+click for Mac) and select “Save link as”.
      +
    • +
    • To download the “sample_data_Shigella_tree.csv” with additional information on each sample, right-click this link (Cmd+click for Mac) and select “Save link as”.
      +
    • +
    • To see the new, created subset-tree, right-click this link (Cmd+click for Mac) and select “Save link as”. The .txt file will download to your computer.
    • +
    +

    You can then import the .txt files with read.tree() from the ape package, as explained in the page.

    +
    +
    ape::read.tree("Shigella_tree.txt")
    +
    +
    +
    +

    Standardization

    +

    See the page on Standardised rates. You can load the data directly from our Github repository on the internet into your R session with the following commands:

    +
    +
    # install/load the rio package
    +pacman::p_load(rio) 
    +
    +##############
    +# Country A
    +##############
    +# import demographics for country A directly from Github
    +A_demo <- import("https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics.csv")
    +
    +# import deaths for country A directly from Github
    +A_deaths <- import("https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryA.csv")
    +
    +##############
    +# Country B
    +##############
    +# import demographics for country B directly from Github
    +B_demo <- import("https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/country_demographics_2.csv")
    +
    +# import deaths for country B directly from Github
    +B_deaths <- import("https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/deaths_countryB.csv")
    +
    +
    +###############
    +# Reference Pop
    +###############
    +# import demographics for country B directly from Github
    +standard_pop_data <- import("https://github.com/appliedepi/epirhandbook_eng/raw/master/data/standardization/world_standard_population_by_sex.csv")
    +
    +
    +
    +

    Time series and outbreak detection

    +

    See the page on Time series and outbreak detection. We use campylobacter cases reported in Germany 2002-2011, as available from the surveillance R package. This dataset has been adapted from the original, in that 3 months of data have been deleted from the end of 2011 for demonstration purposes.

    +

    Click to download Campylobacter in Germany (.xlsx)

    +

    We also use climate data from Germany 2002-2011 (temperature in degrees celsius and rain fail in millimetres) . These were downloaded from the EU Copernicus satellite reanalysis dataset using the ecmwfr package. You will need to download all of these and import them with stars::read_stars() as explained in the time series page.

    +

    Click to download Germany weather 2002 (.nc file)

    +

    Click to download Germany weather 2003 (.nc file)

    +

    Click to download Germany weather 2004 (.nc file)

    +

    Click to download Germany weather 2005 (.nc file)

    +

    Click to download Germany weather 2006 (.nc file)

    +

    Click to download Germany weather 2007 (.nc file)

    +

    Click to download Germany weather 2008 (.nc file)

    +

    Click to download Germany weather 2009 (.nc file)

    +

    Click to download Germany weather 2010 (.nc file)

    +

    Click to download Germany weather 2011 (.nc file)

    +
    +
    +

    Survey analysis

    +

    For the survey analysis page we use fictional mortality survey data based off MSF OCA survey templates. This fictional data was generated as part of the “R4Epis” project.

    +

    Click to download Fictional survey data (.xlsx)

    +

    Click to download Fictional survey data dictionary (.xlsx)

    +

    Click to download Fictional survey population data (.xlsx)

    +
    +
    +

    Shiny

    +

    The page on Dashboards with Shiny demonstrates the construction of a simple app to display malaria data.

    +

    To download the R files that produce the Shiny app:

    +

    You can click here to download the app.R file that contains both the UI and Server code for the Shiny app.

    +

    You can click here to download the facility_count_data.rds file that contains malaria data for the Shiny app. Note that you may need to store it within a “data” folder for the here() file paths to work correctly.

    +

    You can click here to download the global.R file that should run prior to the app opening, as explained in the page.

    +

    You can click here to download the plot_epicurve.R file that is sourced by global.R. Note that you may need to store it within a “funcs” folder for the here() file paths to work correctly.

    + + +
    +
    +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/data_used.jp.qmd b/new_pages/data_used.jp.qmd index 57880b55..8a323e49 100644 --- a/new_pages/data_used.jp.qmd +++ b/new_pages/data_used.jp.qmd @@ -15,7 +15,7 @@ ### ダウンロードリンクの利用 {.unnumbered} -こちらの [リンク](https://github.com/appliedepi/epiRhandbook_eng/raw/master/offline_long/index.jp.html) を右クリックし、「リンク先を別名で保存(Save link as)」を選択すると、素早くダウンロードすることができます。 +こちらの [リンク](https://github.com/appliedepi/epiRhandbook_eng/raw/master/offline_long/Epi_R_Handbook_offline.html) を右クリックし、「リンク先を別名で保存(Save link as)」を選択すると、素早くダウンロードすることができます。 Mac をお使いの場合は、Cmd を押しながらクリックしてください。モバイルの場合は、リンクを長押しし、「リンクを保存(Save link)」を選択すると、ハンドブックがお使いの端末にダウンロードされます。HTML コードが表示された場合は、上記の手順が正しく行われているか確認する、または次の方法をお試しください。 diff --git a/new_pages/data_used.pt.qmd b/new_pages/data_used.pt.qmd index 5c3a2229..a045fe2e 100644 --- a/new_pages/data_used.pt.qmd +++ b/new_pages/data_used.pt.qmd @@ -21,7 +21,7 @@ Você pode baixar a versão offline deste livro como um arquivo HTML para que vo ### Use o link de download {.unnumbered} -Para acesso rápido, **clique com o botão direito** [neste link](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/index.pt.html) **e selecione "Salvar link como"**. +Para acesso rápido, **clique com o botão direito** [neste link](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/Epi_R_Handbook_offline.html) **e selecione "Salvar link como"**. Se estiver em um Mac, use Cmd+clique. Se estiver em um celular, pressione e segure o link e selecione "Salvar link". O livro será baixado para o seu dispositivo. Se uma tela com código HTML bruto for exibida, certifique-se de seguir as instruções acima ou tente a Opção 2. diff --git a/new_pages/data_used.qmd b/new_pages/data_used.qmd index e796394f..0b091cd2 100644 --- a/new_pages/data_used.qmd +++ b/new_pages/data_used.qmd @@ -10,28 +10,32 @@ You can download the offline version of this handbook as an HTML file so that you can view the file in your web browser even if you no longer have internet access. If you are considering offline use of the Epi R Handbook here are a few things to consider: -* When you open the file it may take a minute or two for the images and the Table of Contents to load -* The offline handbook has a slightly different layout - one very long page with Table of Contents on the left. To search for specific terms use Ctrl+f (Cmd-f) -* See the [Suggested packages](packages_suggested.qmd) page to assist you with installing appropriate R packages before you lose internet connectivity -* Install our R package **epirhandbook** that contains all the example data (install process described below) +* When you open the file it may take a minute or two for the images and the 'Table of Contents' to load. +* The offline handbook has a slightly different layout - one very long page with Table of Contents on the left. To search for specific terms use Ctrl+f (Cmd-f). +* See the [Suggested packages](packages_suggested.qmd) page to assist you with installing appropriate R packages before you lose internet connectivity. You will not be able to install new packages without internet access. +* Install our R package **epirhandbook** that contains all the example data (install process described below). **There are two ways you can download the handbook:** -### Use download link {.unnumbered} +### Option 1: Use download link {.unnumbered} -For quick access, **right-click** [this link](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/index.html) **and select "Save link as"**. +For quick access, **right-click** [this link](https://github.com/appliedepi/epiRhandbook_eng/raw/master/offline_long/index.en.html) **and select "Save link as"**. If on a Mac, use Cmd+click. If on a mobile, press and hold the link and select "Save link". The handbook will download to your device. If a screen with raw HTML code appears, ensure you have followed the above instructions or try Option 2. +### Option 2: Use our R package {.unnumbered} -### Use our R package {.unnumbered} +We offer an R package called **epirhandbook**. It includes a function `download_book()` that downloads the handbook file from our Github repository to your computer. If you wish to download the book in a language other than English, you can specify this by specifying the language. -We offer an R package called **epirhandbook**. It includes a function `download_book()` that downloads the handbook file from our Github repository to your computer. +```{eval = F} +download_book(fr) +``` +Currently we support French (fr), German (de), Spanish (es), Portuguese (pt), Vietnamese (vn), Japanese (jp), Turkish (tr), and Russian (ru). This package also contains a function `get_data()` that downloads all the example data to your computer. @@ -86,7 +90,7 @@ pacman::p_load(epirhandbook) Next, use the package's function `get_data()` to download the example data to your computer. Run `get_data("all")` to get *all* the example data, or provide a specific file name and extension within the quotes to retrieve only one file. -The data have already been downloaded with the package, and simply need to be transferred out to a folder on your computer. A pop-up window will appear, allowing you to select a save folder location. We suggest you create a new "data" folder as there are about 30 files (including example data and example outputs). +The data have already been downloaded with the package, and simply need to be transferred out to a folder on your computer. A pop-up window will appear, allowing you to select a save folder location. We suggest you create a new "data" folder as there are almost 80 files (including example data and example outputs). ```{r, eval=F} # download all the example data into a folder on your computer @@ -184,20 +188,24 @@ The [Contact Tracing](contact_tracing.qmd) page demonstrated analysis of contact the contact follow-up data (.rds file) + + Click to download + the contact follow-up data (.rds file) + -**_NOTE:_** Structured contact tracing data from other software (e.g. KoBo, DHIS2 Tracker, CommCare) may look different. If you would like to contribute alternative sample data or content for this page, please [contact us](#contact_us). +**_NOTE:_** Structured contact tracing data from other software (e.g. KoBo, DHIS2 Tracker, CommCare) may look different. If you would like to contribute alternative sample data or content for this page, please [contact us](index.qmd). -**_TIP:_** If you are deploying Go.Data and want to connect to your instance's API, see the Import and export page [(API section)](#import_api) and the [Go.Data Community of Practice](https://community-godata.who.int/). +**_TIP:_** If you are deploying Go.Data and want to connect to your instance's API, see the Import and export page [(API section)](#import_api) and the [Go.Data Community of Practice](https://worldhealthorganization.github.io/godata/). #### GIS {.unnumbered} -Shapefiles have many sub-component files, each with a different file extention. One file will have the ".shp" extension, but others may have ".dbf", ".prj", etc. +Shapefiles have many sub-component files, each with a different file extention. One file will have the ".shp" extension, but others may have ".dbf", ".prj", etc. You will need to have all of these different files within the same folder to use the shapefile. The [GIS basics](gis.qmd) page provides links to the *Humanitarian Data Exchange* website where you can download the shapefiles directly as zipped files. -For example, the health facility points data can be downloaded [here](https://data.humdata.org/dataset/hotosm_sierra_leone_health_facilities). Download "hotosm_sierra_leone_health_facilities_points_shp.zip". Once saved to your computer, "unzip" the folder. You will see several files with different extensions (e.g. ".shp", ".prj", ".shx") - all these must be saved to the same folder on your computer. Then to import into R, provide the file path and name of the ".shp" file to `st_read()` from the **sf** package (as described in the [GIS basics](gis.qmd) page). +For example, the health facility points data can be downloaded [here](https://data.humdata.org/dataset/hotosm_sle_health_facilities). Download "hotosm_sierra_leone_health_facilities_points_shp.zip". Once saved to your computer, "unzip" the folder. You will see several files with different extensions (e.g. ".shp", ".prj", ".shx") - all these must be saved to the same folder on your computer. Then to import into R, provide the file path and name of the ".shp" file to `st_read()` from the **sf** package (as described in the [GIS basics](gis.qmd) page). If you follow Option 1 to download all the example data (via our R package **epirhandbook**), all the shapefiles are included. @@ -211,7 +219,7 @@ knitr::include_graphics(here::here("images", "download_shp.png")) #### Phylogenetic trees {.unnumbered} -See the page on [Phylogenetic trees](phylogenetic_trees.qmd). Newick file of phylogenetic tree constructed from whole genome sequencing of 299 Shigella sonnei samples and corresponding sample data (converted to a text file). The Belgian samples and resulting data are kindly provided by the Belgian NRC for Salmonella and Shigella in the scope of a project conducted by an ECDC EUPHEM Fellow, and will also be published in a manuscript. The international data are openly available on public databases (ncbi) and have been previously published. +See the page on [Phylogenetic trees](phylogenetic_trees.qmd). Newick file of phylogenetic tree constructed from whole genome sequencing of 299 Shigella sonnei samples and corresponding sample data (converted to a text file). The Belgian samples and resulting data are kindly provided by the Belgian NRC for Salmonella and Shigella in the scope of a project conducted by an [ECDC EUPHEM Fellow](https://www.ecdc.europa.eu/en/epiet-euphem), and are published [here](https://www.mdpi.com/2076-2607/9/4/767) with the sequences found [here](https://www.ncbi.nlm.nih.gov/bioproject/PRJNA698782). The international data are openly available on public databases ([National Center for Biotechnology Information, NCBI](https://www.ncbi.nlm.nih.gov/)) and have been previously published. * To download the "Shigella_tree.txt" phylogenetic tree file, right-click this [link](https://github.com/appliedepi/epirhandbook_eng/raw/master/data/phylo/Shigella_tree.txt) (Cmd+click for Mac) and select "Save link as". * To download the "sample_data_Shigella_tree.csv" with additional information on each sample, right-click this [link](https://github.com/appliedepi/epirhandbook_eng/raw/master/data/phylo/sample_data_Shigella_tree.csv) (Cmd+click for Mac) and select "Save link as". @@ -264,7 +272,7 @@ standard_pop_data <- import("https://github.com/appliedepi/epirhandbook_eng/raw/ #### Time series and outbreak detection {#data_outbreak .unnumbered} -See the page on [Time series and outbreak detection](epidemic_models.qmd). We use campylobacter cases reported in Germany 2002-2011, as available from the **surveillance** R package. (*nb.* this dataset has been adapted from the original, in that 3 months of data have been deleted from the end of 2011 for demonstration purposes) +See the page on [Time series and outbreak detection](epidemic_models.qmd). We use campylobacter cases reported in Germany 2002-2011, as available from the **surveillance** R package. This dataset has been adapted from the original, in that 3 months of data have been deleted from the end of 2011 for demonstration purposes. Click to download @@ -327,7 +335,7 @@ We also use climate data from Germany 2002-2011 (temperature in degrees celsius #### Survey analysis {#data_survey .unnumbered} -For the [survey analysis](https://epirhandbook.com/survey-analysis.html) page we use fictional mortality survey data based off MSF OCA survey templates. This fictional data was generated as part of the ["R4Epis" project](https://r4epis.netlify.app/). +For the [survey analysis](https://epirhandbook.com/new_pages/survey_analysis.html) page we use fictional mortality survey data based off MSF OCA survey templates. This fictional data was generated as part of the ["R4Epis" project](https://r4epis.netlify.app/). Click to download @@ -357,12 +365,12 @@ You can - click here to download the facility_count_data.rds file that contains malaria data for the Shiny app. Note that you may need to store it within a "data" folder for the here() file paths to work correctly. + click here to download the facility_count_data.rds file that contains malaria data for the Shiny app. Note that you may need to store it within a "data" folder for the `here()` file paths to work correctly. You can click here to download the global.R file that should run prior to the app opening, as explained in the page. You can - click here to download the plot_epicurve.R file that is sourced by global.R. Note that you may need to store it within a "funcs" folder for the here() file paths to work correctly. + click here to download the plot_epicurve.R file that is sourced by global.R. Note that you may need to store it within a "funcs" folder for the `here()` file paths to work correctly. diff --git a/new_pages/data_used.ru.qmd b/new_pages/data_used.ru.qmd index 1effec15..598f8f30 100644 --- a/new_pages/data_used.ru.qmd +++ b/new_pages/data_used.ru.qmd @@ -21,7 +21,7 @@ ### Использовать ссылку для скачивания {.unnumbered} -Ждя быстрого доступа **правый щелчок** [по этой ссылке](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/index.ru.html) **и выберите "Save link as - сохранить ссылку как"**. +Ждя быстрого доступа **правый щелчок** [по этой ссылке](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/Epi_R_Handbook_offline.html) **и выберите "Save link as - сохранить ссылку как"**. Если вы работаете на компьютере Mac, используйте Cmd+щелчок. Если вы используете мобильный телефон, нажмите и удерживайте ссылку, затем выберите "Сохранить ссылку". Руководство будет загружено на ваше устройство. Если появится окно с необработанным HTML-кодом, убедитесь, что вы выполнили приведенные выше инструкции, или попробуйте вариант 2. diff --git a/new_pages/data_used.tr.qmd b/new_pages/data_used.tr.qmd index 32a7c4df..8c182a50 100644 --- a/new_pages/data_used.tr.qmd +++ b/new_pages/data_used.tr.qmd @@ -21,7 +21,7 @@ ### İndirme linkini kullanarak {.unnumbered} -Linke hızlıca erişebilmek için, **bu linki sağ fare tuşunuzla tıklayın** [link](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/index.tr.html) **ve "Farklı Kaydet" seçeneğini seçin**. +Linke hızlıca erişebilmek için, **bu linki sağ fare tuşunuzla tıklayın** [link](https://github.com/appliedepi/epirhandbook_eng/raw/master/offline_long/Epi_R_Handbook_offline.html) **ve "Farklı Kaydet" seçeneğini seçin**. Mac kullanıyorsanız, Cmd'ye basılı tutarak farenizle tıklayın. Cep telefonu kullanıyorsanız, bağlantıyı basılı tutun ve "Bağlantıyı kaydet"i seçin. Kitap cihazınıza indirilecektir. Görseller olmadan HTML kodu içeren bir ekran görürseniz, yukarıdaki talimatları uyguladığınızdan emin olun veya ikinci seçeneği deneyin. diff --git a/new_pages/data_used.vn.qmd b/new_pages/data_used.vn.qmd index 0786ace2..cb2bde21 100644 --- a/new_pages/data_used.vn.qmd +++ b/new_pages/data_used.vn.qmd @@ -15,7 +15,7 @@ Bạn có thể tải xuống phiên bản ngoại tuyến của sổ tay này d ### Sử dụng link download {.unnumbered} -Để truy cập nhanh, **nháy phải chuột** vào [link này](https://github.com/appliedepi/epirhandbook_vn/tree/master/offline_long/index.vn.html) **và lựa chọn "Save link as"**. +Để truy cập nhanh, **nháy phải chuột** vào [link này](https://github.com/appliedepi/epirhandbook_vn/tree/master/offline_long/Epi_R_Handbook_offline.html) **và lựa chọn "Save link as"**. Nếu trên máy Mac, hãy sử dụng Cmd + Nhấp chuột. Nếu trên điện thoại di động, hãy bấm và giữ liên kết và chọn "Save link". Sổ tay sẽ tải xuống thiết bị của bạn. Nếu trên màn hình xuất hiện mã HTML gốc, hãy đảm bảo bạn đã làm đúng theo các hướng dẫn bên trên hoặc thử Phương án 2. diff --git a/new_pages/dates.html b/new_pages/dates.html new file mode 100644 index 00000000..f04410e1 --- /dev/null +++ b/new_pages/dates.html @@ -0,0 +1,2080 @@ + + + + + + + + + +The Epidemiologist R Handbook - 9  Working with dates + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    9  Working with dates

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Working with dates in R requires more attention than working with other object classes. Below, we offer some tools and example to make this process less painful. Luckily, dates can be wrangled easily with practice, and with a set of helpful packages such as lubridate.

    +

    Upon import of raw data, R often interprets dates as character objects - this means they cannot be used for general date operations such as making time series and calculating time intervals. To make matters more difficult, there are many ways a date can be formatted and you must help R know which part of a date represents what (month, day, hour, etc.).

    +

    Dates in R are their own class of object - the Date class. It should be noted that there is also a class that stores objects with date and time. Date time objects are formally referred to as POSIXt, POSIXct, and/or POSIXlt classes (the difference isn’t important). These objects are informally referred to as datetime classes.

    +
      +
    • It is important to make R recognize when a column contains dates.
      +
    • +
    • Dates are an object class and can be tricky to work with.
      +
    • +
    • Here we present several ways to convert date columns to Date class.
    • +
    + +
    +

    9.1 Preparation

    +
    +

    Load packages

    +

    This code chunk shows the loading of packages required for this page. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.

    +
    +
    # Checks if package is installed, installs if necessary, and loads package for current session
    +
    +pacman::p_load(
    +  lubridate,  # general package for handling and converting dates  
    +  parsedate,  # has function to "guess" messy dates
    +  aweek,      # another option for converting dates to weeks, and weeks to dates
    +  zoo,        # additional date/time functions
    +  here,       # file management
    +  rio,        # data import/export
    +  tidyverse)  # data management and visualization  
    +
    +
    +
    +

    Import data

    +

    We import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow along step-by-step, see instruction in the Download handbook and data page. We assume the file is in the working directory so no sub-folders are specified in this file path.

    +
    +
    +
    Warning: The `trust` argument of `import()` should be explicit for serialization formats
    +as of rio 1.0.3.
    +ℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.
    +ℹ The deprecated feature was likely used in the rio package.
    +  Please report the issue at <https://github.com/gesistsa/rio/issues>.
    +
    +
    +
    +
    linelist <- import("linelist_cleaned.xlsx")
    +
    + +
    +
    +
    +

    9.2 Current date

    +

    You can get the current “system” date or system datetime of your computer by doing the following with base R.

    +
    +
    # get the system date - this is a DATE class
    +Sys.Date()
    +
    +
    [1] "2024-09-08"
    +
    +
    # get the system time - this is a DATETIME class
    +Sys.time()
    +
    +
    [1] "2024-09-08 11:03:46 BST"
    +
    +
    +

    With the lubridate package these can also be returned with today() and now(), respectively. date() returns the current date and time with weekday and month names.

    + +
    +
    +

    9.3 Convert to Date

    +

    After importing a dataset into R, date column values may look like “1989/12/30”, “05/06/2014”, or “13 Jan 2020”. In these cases, R is likely still treating these values as Character values. R must be told that these values are dates… and what the format of the date is (which part is Day, which is Month, which is Year, etc).

    +

    Once told, R converts these values to class Date. In the background, R will store the dates as numbers (the number of days from its “origin” date 1 Jan 1970). You will not interface with the date number often, but this allows for R to treat dates as continuous variables and to allow special operations such as calculating the distance between dates.

    +

    By default, values of class Date in R are displayed as YYYY-MM-DD. Later in this section we will discuss how to change the display of date values.

    +

    Below we present two approaches to converting a column from character values to class Date.

    +

    TIP: You can check the current class of a column with base R function class(), like class(linelist$date_onset).

    +
    +

    base R

    +

    as.Date() is the standard, base R function to convert an object or column to class Date (note capitalization of “D”).

    +

    Use of as.Date() requires that:

    +
      +
    • You specify the existing format of the raw character date or the origin date if supplying dates as numbers (see section on Excel dates)
      +
    • +
    • If used on a character column, all date values must have the same exact format (if this is not the case, try parse_date() from the parsedate package)
    • +
    +

    First, check the class of your column with class() from base R. If you are unsure or confused about the class of your data (e.g. you see “POSIXct”, etc.) it can be easiest to first convert the column to class Character with as.character(), and then convert it to class Date.

    +

    Second, within the as.Date() function, use the format = argument to tell R the current format of the character date components - which characters refer to the month, the day, and the year, and how they are separated. If your values are already in one of R’s standard date formats (“YYYY-MM-DD” or “YYYY/MM/DD”) the format = argument is not necessary.

    +

    To format =, provide a character string (in quotes) that represents the current date format using the special “strptime” abbreviations below. For example, if your character dates are currently in the format “DD/MM/YYYY”, like “24/04/1968”, then you would use format = "%d/%m/%Y" to convert the values into dates. Putting the format in quotation marks is necessary. And don’t forget any slashes or dashes!

    +
    +
    # Convert to class date
    +linelist <- linelist %>% 
    +  mutate(date_onset = as.Date(date_of_onset, format = "%d/%m/%Y"))
    +
    +

    Most of the strptime abbreviations are listed below. You can see the complete list by running ?strptime.

    +

    %d = Day number of month (5, 17, 28, etc.)
    +%j = Day number of the year (Julian day 001-366)
    +%a = Abbreviated weekday (Mon, Tue, Wed, etc.)
    +%A = Full weekday (Monday, Tuesday, etc.) %w = Weekday number (0-6, Sunday is 0)
    +%u = Weekday number (1-7, Monday is 1)
    +%W = Week number (00-53, Monday is week start)
    +%U = Week number (01-53, Sunday is week start)
    +%m = Month number (e.g. 01, 02, 03, 04)
    +%b = Abbreviated month (Jan, Feb, etc.)
    +%B = Full month (January, February, etc.)
    +%y = 2-digit year (e.g. 89)
    +%Y = 4-digit year (e.g. 1989)
    +%h = hours (24-hr clock)
    +%m = minutes
    +%s = seconds %z = offset from GMT
    +%Z = Time zone (character)

    +

    TIP: The format = argument of as.Date() is not telling R the format you want the dates to be, but rather how to identify the date parts as they are before you run the command.

    +

    TIP: Be sure that in the format = argument you use the date-part separator (e.g. /, -, or space) that is present in your dates.

    +

    Once the values are in class Date, R will by default display them in the standard format, which is YYYY-MM-DD.

    +
    +
    +

    lubridate

    +

    Converting character objects to dates can be made easier by using the lubridate package. This is a tidyverse package designed to make working with dates and times more simple and consistent than in base R. For these reasons, lubridate is often considered the gold-standard package for dates and time, and is recommended whenever working with them.

    +

    The lubridate package provides several different helper functions designed to convert character objects to dates in an intuitive, and more lenient way than specifying the format in as.Date(). These functions are specific to the rough date format, but allow for a variety of separators, and synonyms for dates (e.g. 01 vs Jan vs January) - they are named after abbreviations of date formats.

    +
    +
    # install/load lubridate 
    +pacman::p_load(lubridate)
    +
    +

    The ymd() function flexibly converts date values supplied as year, then month, then day.

    +
    +
    # read date in year-month-day format
    +ymd("2020-10-11")
    +
    +
    [1] "2020-10-11"
    +
    +
    ymd("20201011")
    +
    +
    [1] "2020-10-11"
    +
    +
    +

    The mdy() function flexibly converts date values supplied as month, then day, then year.

    +
    +
    # read date in month-day-year format
    +mdy("10/11/2020")
    +
    +
    [1] "2020-10-11"
    +
    +
    mdy("Oct 11 20")
    +
    +
    [1] "2020-10-11"
    +
    +
    +

    The dmy() function flexibly converts date values supplied as day, then month, then year.

    +
    +
    # read date in day-month-year format
    +dmy("11 10 2020")
    +
    +
    [1] "2020-10-11"
    +
    +
    dmy("11 October 2020")
    +
    +
    [1] "2020-10-11"
    +
    +
    + + + + +

    If using piping, the conversion of a character column to dates with lubridate might look like this:

    +
    +
    linelist <- linelist %>%
    +  mutate(date_onset = lubridate::dmy(date_onset))
    +
    +

    Once complete, you can run class() to verify the class of the column

    +
    +
    # Check the class of the column
    +class(linelist$date_onset)  
    +
    +

    Once the values are in class Date, R will by default display them in the standard format, which is YYYY-MM-DD.

    +

    Note that the above functions work best with 4-digit years. 2-digit years can produce unexpected results, as lubridate attempts to guess the century.

    +

    To convert a 2-digit year into a 4-digit year (all in the same century) you can convert to class character and then combine the existing digits with a pre-fix using str_glue() from the stringr package (see Characters and strings). Then convert to date.

    +
    +
    two_digit_years <- c("15", "15", "16", "17")
    +str_glue("20{two_digit_years}")
    +
    +
    2015
    +2015
    +2016
    +2017
    +
    +
    +
    +
    +

    Combine columns

    +

    You can use the lubridate functions make_date() and make_datetime() to combine multiple numeric columns into one date column. For example if you have numeric columns onset_day, onset_month, and onset_year in the data frame linelist:

    +
    +
    linelist <- linelist %>% 
    +  mutate(onset_date = make_date(year = onset_year, month = onset_month, day = onset_day))
    +
    + +
    +
    +
    +

    9.4 Excel dates

    +

    In the background, most software store dates as numbers. R stores dates from an origin of 1st January, 1970. Thus, if you run as.numeric(as.Date("1970-01-01)) you will get 0.

    +

    Microsoft Excel stores dates with an origin of either December 30, 1899 (Windows) or January 1, 1904 (Mac), depending on your operating system. See this Microsoft guidance for more information.

    +

    Excel dates often import into R as these numeric values instead of as characters. If the dataset you imported from Excel shows dates as numbers or characters like “41369”… use as.Date() (or lubridate’s as_date() function) to convert, but instead of supplying a “format” as above, supply the Excel origin date to the argument origin =.

    +

    This will not work if the Excel date is stored in R as a character type, so be sure to ensure the number is class Numeric!

    +

    NOTE: You should provide the origin date in R’s default date format (“YYYY-MM-DD”).

    +
    +
    # An example of providing the Excel 'origin date' when converting Excel number dates
    +data_cleaned <- data %>% 
    +  mutate(date_onset = as.numeric(date_onset)) %>%   # ensure class is numeric
    +  mutate(date_onset = as.Date(date_onset, origin = "1899-12-30")) # convert to date using Excel origin
    +
    + +
    +
    +

    9.5 Messy dates

    +

    The function parse_date() from the parsedate package attempts to read a “messy” date column containing dates in many different formats and convert the dates to a standard format. You can read more online about parse_date().

    +

    For example parse_date() would see a vector of the following character dates “03 Jan 2018”, “07/03/1982”, and “08/20/85” and convert them to class Date as: 2018-01-03, 1982-03-07, and 1985-08-20.

    +
    +
    parsedate::parse_date(c("03 January 2018",
    +                        "07/03/1982",
    +                        "08/20/85"))
    +
    +
    [1] "2018-01-03 UTC" "1982-07-03 UTC" "1985-08-20 UTC"
    +
    +
    +
    +
    # An example using parse_date() on the column date_onset
    +linelist <- linelist %>%      
    +  mutate(date_onset = parse_date(date_onset))
    +
    + +
    +
    +

    9.6 Working with date-time class

    +

    As previously mentioned, R also supports a datetime class - a column that contains date and time information. As with the Date class, these often need to be converted from character objects to datetime objects.

    +
    +

    Convert dates with times

    +

    A standard datetime object is formatted with the date first, which is followed by a time component - for example 01 Jan 2020, 16:30. As with dates, there are many ways this can be formatted, and there are numerous levels of precision (hours, minutes, seconds) that can be supplied.

    +

    Luckily, lubridate helper functions also exist to help convert these strings to datetime objects. These functions are extensions of the date helper functions, with _h (only hours supplied), _hm (hours and minutes supplied), or _hms (hours, minutes, and seconds supplied) appended to the end (e.g. dmy_hms()). These can be used as shown:

    +

    Convert datetime with only hours to datetime object

    +
    +
    ymd_h("2020-01-01 16hrs")
    +
    +
    [1] "2020-01-01 16:00:00 UTC"
    +
    +
    ymd_h("2020-01-01 4PM")
    +
    +
    [1] "2020-01-01 16:00:00 UTC"
    +
    +
    +

    Convert datetime with hours and minutes to datetime object

    +
    +
    dmy_hm("01 January 2020 16:20")
    +
    +
    [1] "2020-01-01 16:20:00 UTC"
    +
    +
    +

    Convert datetime with hours, minutes, and seconds to datetime object

    +
    +
    mdy_hms("01 January 2020, 16:20:40")
    +
    +
    [1] "2020-01-20 16:20:40 UTC"
    +
    +
    +

    You can supply time zone but it is ignored. See section later in this page on time zones.

    +
    +
    mdy_hms("01 January 2020, 16:20:40 PST")
    +
    +
    [1] "2020-01-20 16:20:40 UTC"
    +
    +
    +

    When working with a data frame, time and date columns can be combined to create a datetime column using str_glue() from stringr package and an appropriate lubridate function. See the page on Characters and strings for details on stringr.

    +

    In this example, the linelist data frame has a column in format “hours:minutes”. To convert this to a datetime we follow a few steps:

    +
      +
    1. Create a “clean” time of admission column with missing values filled-in with the column median. We do this because lubridate won’t operate on missing values. Combine it with the column date_hospitalisation, and then use the function ymd_hm() to convert.
    2. +
    +
    +
    # packages
    +pacman::p_load(tidyverse, lubridate, stringr)
    +
    +# time_admission is a column in hours:minutes
    +linelist <- linelist %>%
    +  
    +  # when time of admission is not given, assign the median admission time
    +  mutate(
    +    time_admission_clean = ifelse(
    +      is.na(time_admission),         # if time is missing
    +      median(time_admission),        # assign the median
    +      time_admission                 # if not missing keep as is
    +  ) %>%
    +  
    +    # use str_glue() to combine date and time columns to create one character column
    +    # and then use ymd_hm() to convert it to datetime
    +  mutate(
    +    date_time_of_admission = str_glue("{date_hospitalisation} {time_admission_clean}") %>% 
    +      ymd_hm()
    +  )
    +
    +
    +
    +

    Convert times alone

    +

    If your data contain only a character time (hours and minutes), you can convert and manipulate them as times using strptime() from base R. For example, to get the difference between two of these times:

    +
    +
    # raw character times
    +time1 <- "13:45" 
    +time2 <- "15:20"
    +
    +# Times converted to a datetime class
    +time1_clean <- strptime(time1, format = "%H:%M")
    +time2_clean <- strptime(time2, format = "%H:%M")
    +
    +# Difference is of class "difftime" by default, here converted to numeric hours 
    +as.numeric(time2_clean - time1_clean)   # difference in hours
    +
    +
    [1] 1.583333
    +
    +
    +

    Note however that without a date value provided, it assumes the date is today. To combine a string date and a string time together see how to use stringr in the section just above. Read more about strptime() here.

    +

    To convert single-digit numbers to double-digits (e.g. to “pad” hours or minutes with leading zeros to achieve 2 digits), see this “Pad length” section of the Characters and strings page.

    +
    +
    +

    Extract time

    +

    You can extract elements of a time with hour(), minute(), or second() from lubridate.

    +

    Here is an example of extracting the hour, and then classifing by part of the day. We begin with the column time_admission, which is class Character in format “HH:MM”. First, the strptime() is used as described above to convert the characters to datetime class. Then, the hour is extracted with hour(), returning a number from 0-24. Finally, a column time_period is created using logic with case_when() to classify rows into Morning/Afternoon/Evening/Night based on their hour of admission.

    +
    +
    linelist <- linelist %>%
    +  mutate(hour_admit = hour(strptime(time_admission, format = "%H:%M"))) %>%
    +  mutate(time_period = case_when(
    +    hour_admit > 06 & hour_admit < 12 ~ "Morning",
    +    hour_admit >= 12 & hour_admit < 17 ~ "Afternoon",
    +    hour_admit >= 17 & hour_admit < 21 ~ "Evening",
    +    hour_admit >=21 | hour_admit <= 6 ~ "Night"))
    +
    +

    To learn more about case_when() see the page on Cleaning data and core functions.

    + +
    +
    +
    +

    9.7 Working with dates

    +

    lubridate can also be used for a variety of other functions, such as extracting aspects of a date/datetime, performing date arithmetic, or calculating date intervals

    +

    Here we define a date to use for the examples:

    +
    +
    # create object of class Date
    +example_date <- ymd("2020-03-01")
    +
    +
    +

    Extract date components

    +

    You can extract common aspects such as month, day, weekday:

    +
    +
    month(example_date)  # month number
    +
    +
    [1] 3
    +
    +
    day(example_date)    # day (number) of the month
    +
    +
    [1] 1
    +
    +
    wday(example_date)   # day number of the week (1-7)
    +
    +
    [1] 1
    +
    +
    +

    You can also extract time components from a datetime object or column. This can be useful if you want to view the distribution of admission times.

    +
    +
    example_datetime <- ymd_hm("2020-03-01 14:45")
    +
    +hour(example_datetime)     # extract hour
    +minute(example_datetime)   # extract minute
    +second(example_datetime)   # extract second
    +
    +

    There are several options to retrieve weeks. See the section on Epidemiological weeks below.

    +

    Note that if you are seeking to display a date a certain way (e.g. “Jan 2020” or “Thursday 20 March” or “Week 20, 1977”) you can do this more flexibly as described in the section on Date display.

    +
    +
    +

    Date math

    +

    You can add certain numbers of days or weeks using their respective function from lubridate.

    +
    +
    # add 3 days to this date
    +example_date + days(3)
    +
    +
    [1] "2020-03-04"
    +
    +
    # add 7 weeks and subtract two days from this date
    +example_date + weeks(7) - days(2)
    +
    +
    [1] "2020-04-17"
    +
    +
    +
    +
    +

    Date intervals

    +

    The difference between dates can be calculated by:

    +
      +
    1. Ensure both dates are of class date.
      +
    2. +
    3. Use subtraction to return the “difftime” difference between the two dates.
      +
    4. +
    5. If necessary, convert the result to numeric class to perform subsequent mathematical calculations.
    6. +
    +

    Below the interval between two dates is calculated and displayed. You can find intervals by using the subtraction “minus” symbol on values that are class Date. Note, however that the class of the returned value is “difftime” as displayed below, and must be converted to numeric.

    +
    +
    # find the interval between this date and Feb 20 2020 
    +output <- example_date - ymd("2020-02-20")
    +output    # print
    +
    +
    Time difference of 10 days
    +
    +
    class(output)
    +
    +
    [1] "difftime"
    +
    +
    +

    To do subsequent operations on a “difftime”, convert it to numeric with as.numeric().

    +

    This can all be brought together to work with data - for example:

    +
    +
    pacman::p_load(lubridate, tidyverse)   # load packages
    +
    +linelist <- linelist %>%
    +  
    +  # convert date of onset from character to date objects by specifying dmy format
    +  mutate(date_onset = dmy(date_onset),
    +         date_hospitalisation = dmy(date_hospitalisation)) %>%
    +  
    +  # filter out all cases without onset in march
    +  filter(month(date_onset) == 3) %>%
    +    
    +  # find the difference in days between onset and hospitalisation
    +  mutate(days_onset_to_hosp = date_hospitalisation - date_of_onset)
    +
    +

    In a data frame context, if either of the above dates is missing, the operation will fail for that row. This will result in an NA instead of a numeric value. When using this column for calculations, be sure to set the na.rm = argument to TRUE. For example:

    +
    +
    # calculate the median number of days to hospitalisation for all cases where data are available
    +median(linelist_delay$days_onset_to_hosp, na.rm = T)
    +
    + +
    +
    +
    +

    9.8 Date display

    +

    Once dates are the correct class, you often want them to display differently, for example to display as “Monday 05 January” instead of “2018-01-05”. You may also want to adjust the display in order to then group rows by the date elements displayed - for example to group by month-year.

    +
    +

    format()

    +

    Adjust date display with the base R function format(). This function accepts a character string (in quotes) specifying the desired output format in the “%” strptime abbreviations (the same syntax as used in as.Date()). Below are most of the common abbreviations.

    +

    Note: using format() will convert the values to class Character, so this is generally used towards the end of an analysis or for display purposes only! You can see the complete list by running ?strptime.

    +

    %d = Day number of month (5, 17, 28, etc.)
    +%j = Day number of the year (Julian day 001-366)
    +%a = Abbreviated weekday (Mon, Tue, Wed, etc.)
    +%A = Full weekday (Monday, Tuesday, etc.)
    +%w = Weekday number (0-6, Sunday is 0)
    +%u = Weekday number (1-7, Monday is 1)
    +%W = Week number (00-53, Monday is week start)
    +%U = Week number (01-53, Sunday is week start)
    +%m = Month number (e.g. 01, 02, 03, 04)
    +%b = Abbreviated month (Jan, Feb, etc.)
    +%B = Full month (January, February, etc.)
    +%y = 2-digit year (e.g. 89)
    +%Y = 4-digit year (e.g. 1989)
    +%h = hours (24-hr clock)
    +%m = minutes
    +%s = seconds
    +%z = offset from GMT
    +%Z = Time zone (character)

    +

    An example of formatting today’s date:

    +
    +
    # today's date, with formatting
    +format(Sys.Date(), format = "%d %B %Y")
    +
    +
    [1] "08 September 2024"
    +
    +
    # easy way to get full date and time (default formatting)
    +date()
    +
    +
    [1] "Sun Sep  8 11:03:47 2024"
    +
    +
    # formatted combined date, time, and time zone using str_glue() function
    +str_glue("{format(Sys.Date(), format = '%A, %B %d %Y, %z  %Z, ')}{format(Sys.time(), format = '%H:%M:%S')}")
    +
    +
    Sunday, September 08 2024, +0000  UTC, 11:03:47
    +
    +
    # Using format to display weeks
    +format(Sys.Date(), "%Y Week %W")
    +
    +
    [1] "2024 Week 36"
    +
    +
    +

    Note that if using str_glue(), be aware of that within the expected double quotes ” you should only use single quotes (as above).

    +
    +
    +

    Month-Year

    +

    To convert a Date column to Month-year format, we suggest you use the function as.yearmon() from the zoo package. This converts the date to class “yearmon” and retains the proper ordering. In contrast, using format(column, "%Y %B") will convert to class Character and will order the values alphabetically (incorrectly).

    +

    Below, a new column yearmonth is created from the column date_onset, using the as.yearmon() function. The default (correct) ordering of the resulting values are shown in the table.

    +
    +
    # create new column 
    +test_zoo <- linelist %>% 
    +     mutate(yearmonth = zoo::as.yearmon(date_onset))
    +
    +# print table
    +table(test_zoo$yearmon)
    +
    +
    
    +Apr 2014 May 2014 Jun 2014 Jul 2014 Aug 2014 Sep 2014 Oct 2014 Nov 2014 
    +       7       64      100      226      528     1070     1112      763 
    +Dec 2014 Jan 2015 Feb 2015 Mar 2015 Apr 2015 
    +     562      431      306      277      186 
    +
    +
    +

    In contrast, you can see how only using format() does achieve the desired display format, but not the correct ordering.

    +
    +
    # create new column
    +test_format <- linelist %>% 
    +     mutate(yearmonth = format(date_onset, "%b %Y"))
    +
    +# print table
    +table(test_format$yearmon)
    +
    +
    
    +Apr 2014 Apr 2015 Aug 2014 Dec 2014 Feb 2015 Jan 2015 Jul 2014 Jun 2014 
    +       7      186      528      562      306      431      226      100 
    +Mar 2015 May 2014 Nov 2014 Oct 2014 Sep 2014 
    +     277       64      763     1112     1070 
    +
    +
    +

    Note: if you are working within a ggplot() and want to adjust how dates are displayed only, it may be sufficient to provide a strptime format to the date_labels = argument in scale_x_date() - you can use "%b %Y" or "%Y %b". See the ggplot tips page.

    +

    zoo also offers the function as.yearqtr(), and you can use scale_x_yearmon() when using ggplot().

    + +
    +
    +
    +

    9.9 Epidemiological weeks

    +
    +

    lubridate

    +

    See the page on Grouping data for more extensive examples of grouping data by date. Below we briefly describe grouping data by weeks.

    +

    We generally recommend using the floor_date() function from lubridate, with the argument unit = "week". This rounds the date down to the “start” of the week, as defined by the argument week_start =. The default week start is 1 (for Mondays) but you can specify any day of the week as the start (e.g. 7 for Sundays). floor_date() is versitile and can be used to round down to other time units by setting unit = to “second”, “minute”, “hour”, “day”, “month”, or “year”.

    +

    The returned value is the start date of the week, in Date class. Date class is useful when plotting the data, as it will be easily recognized and ordered correctly by ggplot().

    +

    If you are only interested in adjusting dates to display by week in a plot, see the section in this page on Date display. For example when plotting an epicurve you can format the date display by providing the desired strptime “%” nomenclature. For example, use “%Y-%W” or “%Y-%U” to return the year and week number (given Monday or Sunday week start, respectively).

    +
    +
    +

    Weekly counts

    +

    See the page on Grouping data for a thorough explanation of grouping data with count(), group_by(), and summarise(). A brief example is below.

    +
      +
    1. Create a new ‘week’ column with mutate(), using floor_date() with unit = "week"
      +
    2. +
    3. Get counts of rows (cases) per week with count(); filter out any cases with missing date
      +
    4. +
    5. Finish with complete() from tidyr to ensure that all weeks appear in the data - even those with no rows/cases. By default the count values for any “new” rows are NA, but you can make them 0 with the fill = argument, which expects a named list (below, n is the name of the counts column).
    6. +
    +
    +
    # Make aggregated dataset of weekly case counts
    +weekly_counts <- linelist %>% 
    +  drop_na(date_onset) %>%             # remove cases missing onset date
    +  mutate(weekly_cases = floor_date(   # make new column, week of onset
    +    date_onset,
    +    unit = "week")) %>%            
    +  count(weekly_cases) %>%           # group data by week and count rows per group (creates column 'n')
    +  tidyr::complete(                  # ensure all weeks are present, even those with no cases reported
    +    weekly_cases = seq.Date(          # re-define the "weekly_cases" column as a complete sequence,
    +      from = min(weekly_cases),       # from the minimum date
    +      to = max(weekly_cases),         # to the maxiumum date
    +      by = "week"),                   # by weeks
    +    fill = list(n = 0))             # fill-in NAs in the n counts column with 0
    +
    +

    Here are the first rows of the resulting data frame:

    +
    +
    +
    + +
    +
    +
    +
    +

    Epiweek alternatives

    +

    Note that lubridate also has functions week(), epiweek(), and isoweek(), each of which has slightly different start dates and other nuances. Generally speaking though, floor_date() should be all that you need. Read the details for these functions by entering ?week into the console or reading the documentation here.

    +

    You might consider using the package aweek to set epidemiological weeks. You can read more about it on the RECON website. It has the functions date2week() and week2date() in which you can set the week start day with week_start = "Monday". This package is easiest if you want “week”-style outputs (e.g. “2020-W12”). Another advantage of aweek is that when date2week() is applied to a date column, the returned column (week format) is automatically of class Factor and includes levels for all weeks in the time span (this avoids the extra step of complete() described above). However, aweek does not have the functionality to round dates to other time units such as months, years, etc.

    +

    Another alternative for time series which also works well to show a a “week” format (“2020 W12”) is yearweek() from the package tsibble, as demonstrated in the page on Time series and outbreak detection.

    + +
    +
    +
    +

    9.10 Converting dates/time zones

    +

    When data is present in different time time zones, it can often be important to standardise this data in a unified time zone. This can present a further challenge, as the time zone component of data must be coded manually in most cases.

    +

    In R, each datetime object has a timezone component. By default, all datetime objects will carry the local time zone for the computer being used - this is generally specific to a location rather than a named timezone, as time zones will often change in locations due to daylight savings time. It is not possible to accurately compensate for time zones without a time component of a date, as the event a date column represents cannot be attributed to a specific time, and therefore time shifts measured in hours cannot be reasonably accounted for.

    +

    To deal with time zones, there are a number of helper functions in lubridate that can be used to change the time zone of a datetime object from the local time zone to a different time zone. Time zones are set by attributing a valid tz database time zone to the datetime object. A list of these can be found here - if the location you are using data from is not on this list, nearby large cities in the time zone are available and serve the same purpose.

    +

    https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

    +
    +
    # assign the current time to a column
    +time_now <- Sys.time()
    +time_now
    +
    +
    [1] "2024-09-08 11:03:47 BST"
    +
    +
    # use with_tz() to assign a new timezone to the column, while CHANGING the clock time
    +time_london_real <- with_tz(time_now, "Europe/London")
    +
    +# use force_tz() to assign a new timezone to the column, while KEEPING the clock time
    +time_london_local <- force_tz(time_now, "Europe/London")
    +
    +
    +# note that as long as the computer that was used to run this code is NOT set to London time,
    +# there will be a difference in the times 
    +# (the number of hours difference from the computers time zone to london)
    +time_london_real - time_london_local
    +
    +
    Time difference of 0 secs
    +
    +
    +

    This may seem largely abstract, and is often not needed if the user isn’t working across time zones.

    + +
    +
    +

    9.11 Lagging and leading calculations

    +

    lead() and lag() are functions from the dplyr package which help find previous (lagged) or subsequent (leading) values in a vector - typically a numeric or date vector. This is useful when doing calculations of change/difference between time units.

    +

    Let’s say you want to calculate the difference in cases between a current week and the previous one. The data are initially provided in weekly counts as shown below.

    +
    +
    +
    + +
    +
    +

    When using lag() or lead() the order of rows in the dataframe is very important! - pay attention to whether your dates/numbers are ascending or descending.

    +

    First, create a new column containing the value of the previous (lagged) week.

    +
      +
    • Control the number of units back/forward with n = (must be a non-negative integer).
      +
    • +
    • Use default = to define the value placed in non-existing rows (e.g. the first row for which there is no lagged value). By default this is NA.
      +
    • +
    • Use order_by = TRUE if your the rows are not ordered by your reference column.
    • +
    +
    +
    counts <- counts %>% 
    +  mutate(cases_prev_wk = lag(cases_wk, n = 1))
    +
    +
    +
    +
    + +
    +
    +

    Next, create a new column which is the difference between the two cases columns:

    +
    +
    counts <- counts %>% 
    +  mutate(cases_prev_wk = lag(cases_wk, n = 1),
    +         case_diff = cases_wk - cases_prev_wk)
    +
    +
    +
    +
    + +
    +
    +

    You can read more about lead() and lag() in the documentation here or by entering ?lag in your console.

    + +
    +
    +

    9.12 Resources

    +

    lubridate tidyverse page
    +lubridate RStudio cheatsheet
    +R for Data Science page on dates and times
    +Online tutorial Date formats

    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/dates.qmd b/new_pages/dates.qmd index 25cdfb76..3a9e7daf 100644 --- a/new_pages/dates.qmd +++ b/new_pages/dates.qmd @@ -35,14 +35,15 @@ pacman::p_load( zoo, # additional date/time functions here, # file management rio, # data import/export - tidyverse) # data management and visualization + tidyverse # data management and visualization + ) ``` ### Import data {.unnumbered} We import the dataset of cases from a simulated Ebola epidemic. If you want to download the data to follow along step-by-step, see instruction in the [Download handbook and data](data_used.qmd) page. We assume the file is in the working directory so no sub-folders are specified in this file path. -```{r, echo=F} +```{r, echo=F, warning=F, message=F} linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -214,7 +215,12 @@ You can use the **lubridate** functions `make_date()` and `make_datetime()` to c ```{r, eval=F} linelist <- linelist %>% - mutate(onset_date = make_date(year = onset_year, month = onset_month, day = onset_day)) + mutate( + onset_date = make_date( + year = onset_year, + month = onset_month, + day = onset_day) + ) ``` @@ -262,8 +268,40 @@ linelist <- linelist %>% mutate(date_onset = parse_date(date_onset)) ``` +Another function we can use is `fix_date_df` from the [**datefixR** package](https://docs.ropensci.org/datefixR/). This package can take in a wide variety of different, messy, date formats at the same time and set them to a standard format! + +```{r} + +pacman::p_load( + datefixR +) + +#Set up messy dates +messy_data_table <- data.frame( + case = 1:6, + onset_date = c("2014-01-01", + "05 9 14", + "2014 july", + "1st Apr 2014", + "15 le avril 2014", + 2015 + ) + ) + +messy_data_table + +#Fix the dates +fix_date_df( + df = messy_data_table, + col.names = c("onset_date") +) + + +``` +You can see that even with this range of different formats (and languages!) it has managed to format all of our dates in the same way. Note that for dates missing a "month" it uses the default of 7 (July) and those missing a day it defaults to 1 (1st day of the month). These can be customised using the arguments `day.impute = ` and `month.impute = `. +For a list of the languages that can be used, and additional functions and limitations, please see the [website](https://docs.ropensci.org/datefixR/). ## Working with date-time class @@ -283,6 +321,15 @@ ymd_h("2020-01-01 16hrs") ymd_h("2020-01-01 4PM") ``` +If you are missing the time, for example when the datetime object is created by combining columns in a dataset where some of the information is unavailable, you may want to include the argument `truncated = `. This will allow incomplete data to be converted given the available information, rather than defaulting to `NA`. The value you input into `truncated = `, will depend on how many formats can be missing. + +```{r} +ymd_h("2020-01-01") + +ymd_h("2020-01-01", truncated = 2) + +``` + Convert datetime with hours and minutes to datetime object ```{r} @@ -310,7 +357,11 @@ In this example, the `linelist` data frame has a column in format "hours:minutes ```{r, eval = FALSE} # packages -pacman::p_load(tidyverse, lubridate, stringr) +pacman::p_load( + tidyverse, + lubridate, + stringr + ) # time_admission is a column in hours:minutes linelist <- linelist %>% @@ -319,9 +370,9 @@ linelist <- linelist %>% mutate( time_admission_clean = ifelse( is.na(time_admission), # if time is missing - median(time_admission), # assign the median + median(time_admission, na.rm = T), # assign the median time_admission # if not missing keep as is - ) %>% + )) %>% # use str_glue() to combine date and time columns to create one character column # and then use ymd_hm() to convert it to datetime @@ -329,6 +380,7 @@ linelist <- linelist %>% date_time_of_admission = str_glue("{date_hospitalisation} {time_admission_clean}") %>% ymd_hm() ) + ``` @@ -426,9 +478,9 @@ example_date + weeks(7) - days(2) The difference between dates can be calculated by: -1. Ensure both dates are of class date -2. Use subtraction to return the "difftime" difference between the two dates -3. If necessary, convert the result to numeric class to perform subsequent mathematical calculations +1. Ensure both dates are of class date. +2. Use subtraction to return the "difftime" difference between the two dates. +3. If necessary, convert the result to numeric class to perform subsequent mathematical calculations. Below the interval between two dates is calculated and displayed. You can find intervals by using the subtraction "minus" symbol on values that are class Date. Note, however that the class of the returned value is "difftime" as displayed below, and must be converted to numeric. @@ -615,7 +667,7 @@ In R, each *datetime* object has a timezone component. By default, all datetime To deal with time zones, there are a number of helper functions in lubridate that can be used to change the time zone of a datetime object from the local time zone to a different time zone. Time zones are set by attributing a valid tz database time zone to the datetime object. A list of these can be found here - if the location you are using data from is not on this list, nearby large cities in the time zone are available and serve the same purpose. -https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +[https://en.wikipedia.org/wiki/List_of_tz_database_time_zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) ```{r} @@ -665,13 +717,13 @@ Let's say you want to calculate the difference in cases between a current week a DT::datatable(counts, rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) ``` -**When using `lag()` or `lead()` the order of rows in the dataframe is very important! - pay attention to whether your dates/numbers are ascending or descending** +**When using `lag()` or `lead()` the order of rows in the dataframe is very important! - pay attention to whether your dates/numbers are ascending or descending**. First, create a new column containing the value of the previous (lagged) week. -* Control the number of units back/forward with `n = ` (must be a non-negative integer) +* Control the number of units back/forward with `n = ` (must be a non-negative integer). * Use `default = ` to define the value placed in non-existing rows (e.g. the first row for which there is no lagged value). By default this is `NA`. -* Use `order_by = TRUE` if your the rows are not ordered by your reference column +* Use `order_by = TRUE` if your the rows are not ordered by your reference column. ```{r} @@ -695,7 +747,7 @@ counts <- counts %>% DT::datatable(counts, rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) ``` - +\n You can read more about `lead()` and `lag()` in the documentation [here](https://dplyr.tidyverse.org/reference/lead-lag.html) or by entering `?lag` in your console. @@ -705,5 +757,5 @@ You can read more about `lead()` and `lag()` in the documentation [here](https:/ **lubridate** [tidyverse page](https://lubridate.tidyverse.org/) **lubridate** RStudio [cheatsheet](https://rawgit.com/rstudio/cheatsheets/master/lubridate.pdf) R for Data Science page on [dates and times](https://r4ds.had.co.nz/dates-and-times.html) -[Online tutorial](https://www.statmethods.net/input/dates.html) +[Online tutorial](https://campus.datacamp.com/courses/intermediate-r/chapter-5-utilities?ex=12) [Date formats](https://www.r-bloggers.com/2013/08/date-formats-in-r/) diff --git a/new_pages/deduplication.html b/new_pages/deduplication.html new file mode 100644 index 00000000..c2c6842f --- /dev/null +++ b/new_pages/deduplication.html @@ -0,0 +1,1996 @@ + + + + + + + + + +15  De-duplication – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    15  De-duplication

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    This page covers the following de-duplication techniques:

    +
      +
    1. Identifying and removing duplicate rows
    2. +
    3. “Slicing” rows to keep only certain rows (e.g. min or max) from each group of rows
      +
    4. +
    5. “Rolling-up”, or combining values from multiple rows into one row
    6. +
    + +
    +

    15.1 Preparation

    +
    +

    Load packages

    +

    This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.

    +
    +
    pacman::p_load(
    +  tidyverse,   # deduplication, grouping, and slicing functions
    +  janitor,     # function for reviewing duplicates
    +  stringr      # for string searches, can be used in "rolling-up" values
    +  )      
    +
    +
    +
    +

    Import data

    +

    For demonstration, we will use an example dataset that is created with the R code below.

    +

    The data are records of COVID-19 phone encounters, including encounters with contacts and with cases. The columns include recordID (computer-generated), personID, name, date of encounter, time of encounter, the purpose of the encounter (either to interview as a case or as a contact), and symptoms_ever (whether the person in that encounter reported ever having symptoms).

    +

    Here is the code to create the obs dataset:

    +
    +
    obs <- data.frame(
    +  recordID  = c(1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18),
    +  personID  = c(1,1,2,2,3,2,4,5,6,7,2,1,3,3,4,5,5,7,8),
    +  name      = c("adam", "adam", "amrish", "amrish", "mariah", "amrish", "nikhil", "brian", "smita", "raquel", "amrish",
    +                "adam", "mariah", "mariah", "nikhil", "brian", "brian", "raquel", "natalie"),
    +  date      = c("1/1/2020", "1/1/2020", "2/1/2020", "2/1/2020", "5/1/2020", "5/1/2020", "5/1/2020", "5/1/2020", "5/1/2020","5/1/2020", "2/1/2020",
    +                "5/1/2020", "6/1/2020", "6/1/2020", "6/1/2020", "6/1/2020", "7/1/2020", "7/1/2020", "7/1/2020"),
    +  time      = c("09:00", "09:00", "14:20", "14:20", "12:00", "16:10", "13:01", "15:20", "14:20", "12:30", "10:24",
    +                "09:40", "07:25", "08:32", "15:36", "15:31", "07:59", "11:13", "17:12"),
    +  encounter = c(1,1,1,1,1,3,1,1,1,1,2,
    +                2,2,3,2,2,3,2,1),
    +  purpose   = c("contact", "contact", "contact", "contact", "case", "case", "contact", "contact", "contact", "contact", "contact",
    +                "case", "contact", "contact", "contact", "contact", "case", "contact", "case"),
    +  symptoms_ever = c(NA, NA, "No", "No", "No", "Yes", "Yes", "No", "Yes", NA, "Yes",
    +                    "No", "No", "No", "Yes", "Yes", "No","No", "No")) %>% 
    +  mutate(date = as.Date(date, format = "%d/%m/%Y"))
    +
    +
    +

    Here is the data frame

    +

    Use the filter boxes along the top to review the encounters for each person.

    +
    +
    +
    + +
    +
    +

    A few things to note as you review the data:

    +
      +
    • The first two records are 100% complete duplicates including duplicate recordID (must be a computer glitch!).
      +
    • +
    • The second two rows are duplicates, in all columns except for recordID.
      +
    • +
    • Several people had multiple phone encounters, at various dates and times, and as contacts and/or cases.
    • +
    • At each encounter, the person was asked if they had ever had symptoms, and some of this information is missing.
    • +
    +

    And here is a quick summary of the people and the purposes of their encounters, using tabyl() from janitor:

    +
    +
    obs %>% 
    +  tabyl(name, purpose)
    +
    +
        name case contact
    +    adam    1       2
    +  amrish    1       3
    +   brian    1       2
    +  mariah    1       2
    + natalie    1       0
    +  nikhil    0       2
    +  raquel    0       2
    +   smita    0       1
    +
    +
    + +
    +
    +
    +
    +

    15.2 Deduplication

    +

    This section describes how to review and remove duplicate rows in a data frame. It also show how to handle duplicate elements in a vector.

    + +
    +

    Examine duplicate rows

    +

    To quickly review rows that have duplicates, you can use get_dupes() from the janitor package. By default, all columns are considered when duplicates are evaluated - rows returned by the function are 100% duplicates considering the values in all columns.

    +

    In the obs data frame, the first two rows are 100% duplicates - they have the same value in every column (including the recordID column, which is supposed to be unique). The returned data frame automatically includes a new column dupe_count on the right side, showing the number of rows with that combination of duplicate values.

    +
    +
    # 100% duplicates across all columns
    +obs %>% 
    +  janitor::get_dupes()
    +
    +
    +
    +
    + +
    +
    +

    See the original data

    +

    However, if we choose to ignore recordID, the 3rd and 4th rows rows are also duplicates of each other. That is, they have the same values in all columns except for recordID. You can specify specific columns to be ignored in the function using a - minus symbol.

    +
    +
    # Duplicates when column recordID is not considered
    +obs %>% 
    +  janitor::get_dupes(-recordID)         # if multiple columns, wrap them in c()
    +
    +
    +
    +
    + +
    +
    +

    You can also positively specify the columns to consider. Below, only rows that have the same values in the name and purpose columns are returned. Notice how “amrish” now has dupe_count equal to 3 to reflect his three “contact” encounters.

    +

    Scroll left for more rows

    +
    +
    # duplicates based on name and purpose columns ONLY
    +obs %>% 
    +  janitor::get_dupes(name, purpose)
    +
    +
    +
    +
    + +
    +
    +

    See the original data.

    +

    See ?get_dupes for more details, or see this online reference

    + +
    +
    +

    Keep only unique rows

    +

    To keep only unique rows of a data frame, use distinct() from dplyr (as demonstrated in the Cleaning data and core functions page). Rows that are duplicates are removed such that only the first of such rows is kept. By default, “first” means the highest rownumber (order of rows top-to-bottom). Only unique rows remain.

    +

    In the example below, we run distinct() such that the column recordID is excluded from consideration - thus two duplicate rows are removed. The first row (for “adam”) was 100% duplicated and has been removed. Also row 3 (for “amrish”) was a duplicate in every column except recordID (which is not being considered) and so is also removed. The obs dataset n is now nrow(obs)-2, not nrow(obs) rows).

    +

    Scroll to the left to see the entire data frame

    +
    +
    # added to a chain of pipes (e.g. data cleaning)
    +obs %>% 
    +  distinct(across(-recordID), # reduces data frame to only unique rows (keeps first one of any duplicates)
    +           .keep_all = TRUE) 
    +
    +# if outside pipes, include the data as first argument 
    +# distinct(obs)
    +
    +
    +
    +
    + +
    +
    +

    CAUTION: If using distinct() on grouped data, the function will apply to each group.

    +

    Deduplicate based on specific columns

    +

    You can also specify columns to be the basis for de-duplication. In this way, the de-duplication only applies to rows that are duplicates within the specified columns. Unless you set .keep_all = TRUE, all columns not mentioned will be dropped.

    +

    In the example below, the de-duplication only applies to rows that have identical values for name and purpose columns. Thus, “brian” has only 2 rows instead of 3 - his first “contact” encounter and his only “case” encounter. To adjust so that brian’s latest encounter of each purpose is kept, see the tab on Slicing within groups.

    +

    Scroll to the left to see the entire data frame

    +
    +
    # added to a chain of pipes (e.g. data cleaning)
    +obs %>% 
    +  distinct(name, purpose, .keep_all = TRUE) %>%  # keep rows unique by name and purpose, retain all columns
    +  arrange(name)                                  # arrange for easier viewing
    +
    +
    +
    +
    + +
    +
    +

    See the original data.

    + +
    +
    +

    Deduplicate elements in a vector

    +

    The function duplicated() from base R will evaluate a vector (column) and return a logical vector of the same length (TRUE/FALSE). The first time a value appears, it will return FALSE (not a duplicate), and subsequent times that value appears it will return TRUE. Note how NA is treated the same as any other value.

    +
    +
    x <- c(1, 1, 2, NA, NA, 4, 5, 4, 4, 1, 2)
    +duplicated(x)
    +
    +
     [1] FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE
    +
    +
    +

    To return only the duplicated elements, you can use brackets to subset the original vector:

    +
    +
    x[duplicated(x)]
    +
    +
    [1]  1 NA  4  4  1  2
    +
    +
    +

    To return only the unique elements, use unique() from base R. To remove NAs from the output, nest na.omit() within unique().

    +
    +
    unique(x)           # alternatively, use x[!duplicated(x)]
    +
    +
    [1]  1  2 NA  4  5
    +
    +
    unique(na.omit(x))  # remove NAs 
    +
    +
    [1] 1 2 4 5
    +
    +
    + +
    +
    +

    Using base R

    +

    To return duplicate rows

    +

    In base R, you can also see which rows are 100% duplicates in a data frame df with the command duplicated(df) (returns a logical vector of the rows).

    +

    Thus, you can also use the base subset [ ] on the data frame to see the duplicated rows with df[duplicated(df),] (don’t forget the comma, meaning that you want to see all columns!).

    +

    To return unique rows

    +

    See the notes above. To see the unique rows you add the logical negator ! in front of the duplicated() function:
    +df[!duplicated(df),]

    +

    To return rows that are duplicates of only certain columns

    +

    Subset the df that is within the duplicated() parentheses, so this function will operate on only certain columns of the df.

    +

    To specify the columns, provide column numbers or names after a comma (remember, all this is within the duplicated() function).

    +

    Be sure to keep the comma , outside after the duplicated() function as well!

    +

    For example, to evaluate only columns 2 through 5 for duplicates: df[!duplicated(df[, 2:5]),]
    +To evaluate only columns name and purpose for duplicates: df[!duplicated(df[, c("name", "purpose)]),]

    + +
    +
    +
    +

    15.3 Slicing

    +

    To “slice” a data frame to apply a filter on the rows by row number/position. This becomes particularly useful if you have multiple rows per functional group (e.g. per “person”) and you only want to keep one or some of them.

    +

    The basic slice() function accepts numbers and returns rows in those positions. If the numbers provided are positive, only they are returned. If negative, those rows are not returned. Numbers must be either all positive or all negative.

    +
    +
    obs %>% 
    +     slice(4)  # return the 4th row
    +
    +
      recordID personID   name       date  time encounter purpose symptoms_ever
    +1        3        2 amrish 2020-01-02 14:20         1 contact            No
    +
    +
    +
    +
    obs %>% 
    +     slice(c(2, 4))  # return rows 2 and 4
    +
    +
      recordID personID   name       date  time encounter purpose symptoms_ever
    +1        1        1   adam 2020-01-01 09:00         1 contact          <NA>
    +2        3        2 amrish 2020-01-02 14:20         1 contact            No
    +
    +
    +
    +
    obs %>% 
    +     slice(c(2:4))  # return rows 2 through 4
    +
    +
      recordID personID   name       date  time encounter purpose symptoms_ever
    +1        1        1   adam 2020-01-01 09:00         1 contact          <NA>
    +2        2        2 amrish 2020-01-02 14:20         1 contact            No
    +3        3        2 amrish 2020-01-02 14:20         1 contact            No
    +
    +
    +

    See the original data.

    +

    There are several variations: These should be provided with a column and a number of rows to return (to n =).

    +
      +
    • slice_min() and slice_max() keep only the row(s) with the minimium or maximum value(s) of the specified column. This also works to return the “min” and “max” of ordered factors
      +
    • +
    • slice_head() and slice_tail() - keep only the first or last row(s)
      +
    • +
    • slice_sample() - keep only a random sample of the rows
    • +
    +
    +
    obs %>% 
    +     slice_max(encounter, n = 1)  # return rows with the largest encounter number
    +
    +
      recordID personID   name       date  time encounter purpose symptoms_ever
    +1        5        2 amrish 2020-01-05 16:10         3    case           Yes
    +2       13        3 mariah 2020-01-06 08:32         3 contact            No
    +3       16        5  brian 2020-01-07 07:59         3    case            No
    +
    +
    +

    Use arguments n = or prop = to specify the number or proportion of rows to keep. If not using the function in a pipe chain, provide the data argument first (e.g. slice(data, n = 2)). See ?slice for more information.

    +

    Other arguments:

    +
      +
    • .order_by = used in slice_min() and slice_max() this is a column to order by before slicing
    • +
    • with_ties = TRUE by default, meaning ties are kept
    • +
    • .preserve = FALSE by default. If TRUE then the grouping structure is re-calculated after slicing
    • +
    • weight_by = Optional, numeric column to weight by (bigger number more likely to get sampled). Also * replace = for whether sampling is done with/without replacement
    • +
    +

    TIP: When using slice_max() and slice_min(), be sure to specify/write the n = (e.g. n = 2, not just 2). Otherwise you may get an error Error:is not empty.

    +

    NOTE: You may encounter the function top_n(), which has been superseded by the slice functions.

    + +
    +

    Slice with groups

    +

    The slice_*() functions can be very useful if applied to a grouped data frame because the slice operation is performed on each group separately. Use the function group_by() in conjunction with slice() to group the data to take a slice from each group.

    +

    This is helpful for de-duplication if you have multiple rows per person but only want to keep one of them. You first use group_by() with key columns that are the same per person, and then use a slice function on a column that will differ among the grouped rows.

    +

    In the example below, to keep only the latest encounter per person, we group the rows by name and then use slice_max() with n = 1 on the date column.

    +

    Be aware! To apply a function like slice_max() on dates, the date column must be class Date.

    +

    By default, “ties” (e.g. same date in this scenario) are kept, and we would still get multiple rows for some people (e.g. adam). To avoid this we set with_ties = FALSE. We get back only one row per person.

    +

    CAUTION: If using arrange(), specify .by_group = TRUE to have the data arranged within each group.

    +

    DANGER: If with_ties = FALSE, the first row of a tie is kept. This may be deceptive. See how for Mariah, she has two encounters on her latest date (6 Jan) and the first (earliest) one was kept. Likely, we want to keep her later encounter on that day. See how to “break” these ties in the next example.

    +
    +
    obs %>% 
    +  group_by(name) %>%       # group the rows by 'name'
    +  slice_max(date,          # keep row per group with maximum date value 
    +            n = 1,         # keep only the single highest row 
    +            with_ties = F) # if there's a tie (of date), take the first row
    +
    +
    +
    +
    + +
    +
    +

    Above, for example we can see that only Amrish’s row on 5 Jan was kept, and only Brian’s row on 7 Jan was kept. See the original data.

    +

    Breaking “ties”

    +

    Multiple slice statements can be run to “break ties”. In this case, if a person has multiple encounters on their latest date, the encounter with the latest time is kept (lubridate::hm() is used to convert the character times to a sortable time class).
    +Note how now, the one row kept for “Mariah” on 6 Jan is encounter 3 from 08:32, not encounter 2 at 07:25.

    +
    +
    # Example of multiple slice statements to "break ties"
    +obs %>%
    +  group_by(name) %>%
    +  
    +  # FIRST - slice by latest date
    +  slice_max(date, n = 1, with_ties = TRUE) %>% 
    +  
    +  # SECOND - if there is a tie, select row with latest time; ties prohibited
    +  slice_max(lubridate::hm(time), n = 1, with_ties = FALSE)
    +
    +
    +
    +
    + +
    +
    +

    In the example above, it would also have been possible to slice by encounter number, but we showed the slice on date and time for example purposes.

    +

    TIP: To use slice_max() or slice_min() on a “character” column, mutate it to an ordered factor class!

    +

    See the original data.

    + +
    +
    +

    Keep all but mark them

    +

    If you want to keep all records but mark only some for analysis, consider a two-step approach utilizing a unique recordID/encounter number:

    +
      +
    1. Reduce/slice the orginal data frame to only the rows for analysis. Save/retain this reduced data frame.
      +
    2. +
    3. In the original data frame, mark rows as appropriate with case_when(), based on whether their record unique identifier (recordID in this example) is present in the reduced data frame.
    4. +
    +
    +
    # 1. Define data frame of rows to keep for analysis
    +obs_keep <- obs %>%
    +  group_by(name) %>%
    +  slice_max(encounter, 
    +            n = 1, 
    +            with_ties = FALSE) # keep only latest encounter per person
    +
    +
    +# 2. Mark original data frame
    +obs_marked <- obs %>%
    +
    +  # make new dup_record column
    +  mutate(dup_record = case_when(
    +    
    +    # if record is in obs_keep data frame
    +    recordID %in% obs_keep$recordID ~ "For analysis", 
    +    
    +    # all else marked as "Ignore" for analysis purposes
    +    TRUE                            ~ "Ignore"))
    +
    +# print
    +obs_marked
    +
    +
       recordID personID    name       date  time encounter purpose symptoms_ever
    +1         1        1    adam 2020-01-01 09:00         1 contact          <NA>
    +2         1        1    adam 2020-01-01 09:00         1 contact          <NA>
    +3         2        2  amrish 2020-01-02 14:20         1 contact            No
    +4         3        2  amrish 2020-01-02 14:20         1 contact            No
    +5         4        3  mariah 2020-01-05 12:00         1    case            No
    +6         5        2  amrish 2020-01-05 16:10         3    case           Yes
    +7         6        4  nikhil 2020-01-05 13:01         1 contact           Yes
    +8         7        5   brian 2020-01-05 15:20         1 contact            No
    +9         8        6   smita 2020-01-05 14:20         1 contact           Yes
    +10        9        7  raquel 2020-01-05 12:30         1 contact          <NA>
    +11       10        2  amrish 2020-01-02 10:24         2 contact           Yes
    +12       11        1    adam 2020-01-05 09:40         2    case            No
    +13       12        3  mariah 2020-01-06 07:25         2 contact            No
    +14       13        3  mariah 2020-01-06 08:32         3 contact            No
    +15       14        4  nikhil 2020-01-06 15:36         2 contact           Yes
    +16       15        5   brian 2020-01-06 15:31         2 contact           Yes
    +17       16        5   brian 2020-01-07 07:59         3    case            No
    +18       17        7  raquel 2020-01-07 11:13         2 contact            No
    +19       18        8 natalie 2020-01-07 17:12         1    case            No
    +     dup_record
    +1        Ignore
    +2        Ignore
    +3        Ignore
    +4        Ignore
    +5        Ignore
    +6  For analysis
    +7        Ignore
    +8        Ignore
    +9  For analysis
    +10       Ignore
    +11       Ignore
    +12 For analysis
    +13       Ignore
    +14 For analysis
    +15 For analysis
    +16       Ignore
    +17 For analysis
    +18 For analysis
    +19 For analysis
    +
    +
    +
    +
    +
    + +
    +
    +

    See the original data.

    + +
    +
    +

    Calculate row completeness

    +

    Create a column that contains a metric for the row’s completeness (non-missingness). This could be helpful when deciding which rows to prioritize over others when de-duplicating/slicing.

    +

    In this example, “key” columns over which you want to measure completeness are saved in a vector of column names.

    +

    Then the new column key_completeness is created with mutate(). The new value in each row is defined as a calculated fraction: the number of non-missing values in that row among the key columns, divided by the number of key columns.

    +

    This involves the function rowSums() from base R. Also used is ., which within piping refers to the data frame at that point in the pipe (in this case, it is being subset with brackets []).

    +

    Scroll to the right to see more rows

    +
    +
    # create a "key variable completeness" column
    +# this is a *proportion* of the columns designated as "key_cols" that have non-missing values
    +
    +key_cols = c("personID", "name", "symptoms_ever")
    +
    +obs %>% 
    +  mutate(key_completeness = rowSums(!is.na(.[,key_cols]))/length(key_cols)) 
    +
    +
    +
    +
    + +
    +
    +

    See the original data.

    + +
    +
    +
    +

    15.4 Roll-up values

    +

    This section describes:

    +
      +
    1. How to “roll-up” values from multiple rows into just one row, with some variations
    2. +
    3. Once you have “rolled-up” values, how to overwrite/prioritize the values in each cell
    4. +
    +

    This tab uses the example dataset from the Preparation tab.

    + +
    +

    Roll-up values into one row

    +

    The code example below uses group_by() and summarise() to group rows by person, and then paste together all unique values within the grouped rows. Thus, you get one summary row per person. A few notes:

    +
      +
    • A suffix is appended to all new columns (“_roll” in this example)
    • +
    • If you want to show only unique values per cell, then wrap the na.omit() with unique()
    • +
    • na.omit() removes NA values, but if this is not desired it can be removed paste0(.x)
    • +
    +
    +
    # "Roll-up" values into one row per group (per "personID") 
    +cases_rolled <- obs %>% 
    +  
    +  # create groups by name
    +  group_by(personID) %>% 
    +  
    +  # order the rows within each group (e.g. by date)
    +  arrange(date, .by_group = TRUE) %>% 
    +  
    +  # For each column, paste together all values within the grouped rows, separated by ";"
    +  summarise(
    +    across(everything(),                           # apply to all columns
    +           ~paste0(na.omit(.x), collapse = "; "))) # function is defined which combines non-NA values
    +
    +

    The result is one row per group (ID), with entries arranged by date and pasted together. Scroll to the left to see more rows

    +
    +
    +
    + +
    +
    +

    See the original data.

    +

    This variation shows unique values only:

    +
    +
    # Variation - show unique values only 
    +cases_rolled <- obs %>% 
    +  group_by(personID) %>% 
    +  arrange(date, .by_group = TRUE) %>% 
    +  summarise(
    +    across(everything(),                                   # apply to all columns
    +           ~paste0(unique(na.omit(.x)), collapse = "; "))) # function is defined which combines unique non-NA values
    +
    +
    +
    +
    + +
    +
    +

    This variation appends a suffix to each column.
    +In this case “_roll” to signify that it has been rolled:

    +
    +
    # Variation - suffix added to column names 
    +cases_rolled <- obs %>% 
    +  group_by(personID) %>% 
    +  arrange(date, .by_group = TRUE) %>% 
    +  summarise(
    +    across(everything(),                
    +           list(roll = ~paste0(na.omit(.x), collapse = "; ")))) # _roll is appended to column names
    +
    +
    +
    +
    + +
    +
    + +
    +
    +

    Overwrite values/hierarchy

    +

    If you then want to evaluate all of the rolled values, and keep only a specific value (e.g. “best” or “maximum” value), you can use mutate() across the desired columns, to implement case_when(), which uses str_detect() from the stringr package to sequentially look for string patterns and overwrite the cell content.

    +
    +
    # CLEAN CASES
    +#############
    +cases_clean <- cases_rolled %>% 
    +    
    +    # clean Yes-No-Unknown vars: replace text with "highest" value present in the string
    +    mutate(across(c(contains("symptoms_ever")),                     # operates on specified columns (Y/N/U)
    +             list(mod = ~case_when(                                 # adds suffix "_mod" to new cols; implements case_when()
    +               
    +               str_detect(.x, "Yes")       ~ "Yes",                 # if "Yes" is detected, then cell value converts to yes
    +               str_detect(.x, "No")        ~ "No",                  # then, if "No" is detected, then cell value converts to no
    +               str_detect(.x, "Unknown")   ~ "Unknown",             # then, if "Unknown" is detected, then cell value converts to Unknown
    +               TRUE                        ~ as.character(.x)))),   # then, if anything else if it kept as is
    +      .keep = "unused")                                             # old columns removed, leaving only _mod columns
    +
    +

    Now you can see in the column symptoms_ever that if the person EVER said “Yes” to symptoms, then only “Yes” is displayed.

    +
    +
    +
    + +
    +
    +

    See the original data.

    +
    +
    +
    +

    15.5 Probabilistic de-duplication

    +

    Sometimes, you may want to identify “likely” duplicates based on similarity (e.g. string “distance”) across several columns such as name, age, sex, date of birth, etc. You can apply a probabilistic matching algorithm to identify likely duplicates.

    +

    See the page on Joining data for an explanation on this method. The section on Probabilistic Matching contains an example of applying these algorithms to compare a data frame to itself, thus performing probabilistic de-duplication.

    + +
    +
    +

    15.6 Resources

    +

    Much of the information in this page is adapted from these resources and vignettes online:

    +

    datanovia

    +

    dplyr tidyverse reference

    +

    cran janitor vignette

    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/deduplication.qmd b/new_pages/deduplication.qmd index b7b14e7f..aad62937 100644 --- a/new_pages/deduplication.qmd +++ b/new_pages/deduplication.qmd @@ -7,9 +7,9 @@ knitr::include_graphics(here::here("images", "deduplication.png")) This page covers the following de-duplication techniques: -1. Identifying and removing duplicate rows +1. Identifying and removing duplicate rows 2. "Slicing" rows to keep only certain rows (e.g. min or max) from each group of rows -3. "Rolling-up", or combining values from multiple rows into one row +3. "Rolling-up", or combining values from multiple rows into one row @@ -24,7 +24,8 @@ This code chunk shows the loading of packages required for the analyses. In this pacman::p_load( tidyverse, # deduplication, grouping, and slicing functions janitor, # function for reviewing duplicates - stringr) # for string searches, can be used in "rolling-up" values + stringr # for string searches, can be used in "rolling-up" values + ) ``` ### Import data {.unnumbered} @@ -66,9 +67,9 @@ DT::datatable(obs, rownames = FALSE, filter = "top", options = list(pageLength = A few things to note as you review the data: -* The first two records are 100% complete duplicates including duplicate `recordID` (must be a computer glitch!) -* The second two rows are duplicates, in all columns *except for `recordID`* -* Several people had multiple phone encounters, at various dates and times, and as contacts and/or cases +* The first two records are 100% complete duplicates including duplicate `recordID` (must be a computer glitch!). +* The second two rows are duplicates, in all columns *except for `recordID`*. +* Several people had multiple phone encounters, at various dates and times, and as contacts and/or cases. * At each encounter, the person was asked if they had **ever** had symptoms, and some of this information is missing. @@ -91,7 +92,7 @@ This section describes how to review and remove duplicate rows in a data frame. To quickly review rows that have duplicates, you can use `get_dupes()` from the **janitor** package. *By default*, all columns are considered when duplicates are evaluated - rows returned by the function are 100% duplicates considering the values in *all* columns. -In the `obs` data frame, the first two rows are *100% duplicates* - they have the same value in every column (including the `recordID` column, which is *supposed* to be unique - it must be some computer glitch). The returned data frame automatically includes a new column `dupe_count` on the right side, showing the number of rows with that combination of duplicate values. +In the `obs` data frame, the first two rows are *100% duplicates* - they have the same value in every column (including the `recordID` column, which is *supposed* to be unique). The returned data frame automatically includes a new column `dupe_count` on the right side, showing the number of rows with that combination of duplicate values. ```{r, eval=F} # 100% duplicates across all columns @@ -123,7 +124,7 @@ obs %>% You can also positively specify the columns to consider. Below, only rows that have the same values in the `name` and `purpose` columns are returned. Notice how "amrish" now has `dupe_count` equal to 3 to reflect his three "contact" encounters. -*Scroll left for more rows** +*Scroll left for more rows* ```{r, eval=F} # duplicates based on name and purpose columns ONLY @@ -265,36 +266,44 @@ To "slice" a data frame to apply a filter on the rows by row number/position. Th The basic `slice()` function accepts numbers and returns rows in those positions. If the numbers provided are positive, only they are returned. If negative, those rows are *not* returned. Numbers must be either all positive or all negative. ```{r} -obs %>% slice(4) # return the 4th row +obs %>% + slice(4) # return the 4th row ``` ```{r} -obs %>% slice(c(2,4)) # return rows 2 and 4 -#obs %>% slice(c(2:4)) # return rows 2 through 4 +obs %>% + slice(c(2, 4)) # return rows 2 and 4 ``` +```{r} +obs %>% + slice(c(2:4)) # return rows 2 through 4 +``` + + See the [original data](#dedup_data). There are several variations: These should be provided with a column and a number of rows to return (to `n = `). -* `slice_min()` and `slice_max()` keep only the row(s) with the minimium or maximum value(s) of the specified column. This also works to return the "min" and "max" of ordered factors. -* `slice_head()` and `slice_tail()` - keep only the *first* or *last* row(s). -* `slice_sample()` - keep only a random sample of the rows. +* `slice_min()` and `slice_max()` keep only the row(s) with the minimium or maximum value(s) of the specified column. This also works to return the "min" and "max" of ordered factors +* `slice_head()` and `slice_tail()` - keep only the *first* or *last* row(s) +* `slice_sample()` - keep only a random sample of the rows ```{r} -obs %>% slice_max(encounter, n = 1) # return rows with the largest encounter number +obs %>% + slice_max(encounter, n = 1) # return rows with the largest encounter number ``` Use arguments `n = ` or `prop = ` to specify the number or proportion of rows to keep. If not using the function in a pipe chain, provide the data argument first (e.g. `slice(data, n = 2)`). See `?slice` for more information. Other arguments: -`.order_by = ` used in `slice_min()` and `slice_max()` this is a column to order by before slicing. -`with_ties = ` TRUE by default, meaning ties are kept. -`.preserve = ` FALSE by default. If TRUE then the grouping structure is re-calculated after slicing. -`weight_by = ` Optional, numeric column to weight by (bigger number more likely to get sampled). Also `replace = ` for whether sampling is done with/without replacement. +* `.order_by = ` used in `slice_min()` and `slice_max()` this is a column to order by before slicing +* `with_ties = ` TRUE by default, meaning ties are kept +* `.preserve = ` FALSE by default. If TRUE then the grouping structure is re-calculated after slicing +* `weight_by = ` Optional, numeric column to weight by (bigger number more likely to get sampled). Also * `replace = ` for whether sampling is done with/without replacement **_TIP:_** When using `slice_max()` and `slice_min()`, be sure to specify/write the `n = ` (e.g. `n = 2`, not just `2`). Otherwise you may get an error `Error: `...` is not empty.` @@ -310,7 +319,9 @@ The `slice_*()` functions can be very useful if applied to a grouped data frame This is helpful for de-duplication if you have multiple rows per person but only want to keep one of them. You first use `group_by()` with key columns that are the same per person, and then use a slice function on a column that will differ among the grouped rows. -In the example below, to keep only the *latest* encounter *per person*, we group the rows by `name` and then use `slice_max()` with `n = 1` on the `date` column. Be aware! To apply a function like `slice_max()` on dates, the date column must be class Date. +In the example below, to keep only the *latest* encounter *per person*, we group the rows by `name` and then use `slice_max()` with `n = 1` on the `date` column. + +**Be aware!** To apply a function like `slice_max()` on dates, the date column must be class Date. By default, "ties" (e.g. same date in this scenario) are kept, and we would still get multiple rows for some people (e.g. adam). To avoid this we set `with_ties = FALSE`. We get back only one row per person. @@ -392,7 +403,9 @@ If you want to keep all records but mark only some for analysis, consider a two- # 1. Define data frame of rows to keep for analysis obs_keep <- obs %>% group_by(name) %>% - slice_max(encounter, n = 1, with_ties = FALSE) # keep only latest encounter per person + slice_max(encounter, + n = 1, + with_ties = FALSE) # keep only latest encounter per person # 2. Mark original data frame @@ -429,7 +442,7 @@ Then the new column `key_completeness` is created with `mutate()`. The new value This involves the function `rowSums()` from **base** R. Also used is `.`, which within piping refers to the data frame at that point in the pipe (in this case, it is being subset with brackets `[]`). -*Scroll to the right to see more rows** +*Scroll to the right to see more rows* ```{r, eval=F} # create a "key variable completeness" column @@ -460,7 +473,7 @@ See the [original data](#dedup_data). This section describes: -1) How to "roll-up" values from multiple rows into just one row, with some variations +1) How to "roll-up" values from multiple rows into just one row, with some variations 2) Once you have "rolled-up" values, how to overwrite/prioritize the values in each cell This tab uses the example dataset from the Preparation tab. @@ -472,9 +485,9 @@ This tab uses the example dataset from the Preparation tab. The code example below uses `group_by()` and `summarise()` to group rows by person, and then paste together all unique values within the grouped rows. Thus, you get one summary row per person. A few notes: -* A suffix is appended to all new columns ("_roll" in this example) -* If you want to show only unique values per cell, then wrap the `na.omit()` with `unique()` -* `na.omit()` removes `NA` values, but if this is not desired it can be removed `paste0(.x)`... +* A suffix is appended to all new columns ("_roll" in this example) +* If you want to show only unique values per cell, then wrap the `na.omit()` with `unique()` +* `na.omit()` removes `NA` values, but if this is not desired it can be removed `paste0(.x)` diff --git a/new_pages/descriptive_statistics.qmd b/new_pages/descriptive_statistics.qmd index 58260c21..0c4da8c6 100644 --- a/new_pages/descriptive_statistics.qmd +++ b/new_pages/descriptive_statistics.qmd @@ -29,7 +29,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). The dataset is imported using the `import()` function from the **rio** package. See the page on [Import and export](importing.qmd) for various ways to import data. -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -104,9 +104,9 @@ summary(linelist$age_years)[[2]] You have several choices when producing tabulation and cross-tabulation summary tables. Some of the factors to consider include code simplicity and ease, the desired output (printed to R console, or as pretty HTML), and what you can do with the data afterward. Consider the thoughts below as you choose the tool for your situation. * Use `tabyl()` from **janitor** to produce and "adorn" tabulations and cross-tabulations - * Cons: requires post-processing (e.g with **flextable**) for publication ready tables. Does not accept multiple variables (need to use **purrr** to iterate) -* Use `count()` and `summarise()` from **dplyr** if preparing data for `ggplot()` or calculating more complex statistics - * Cons: similar to above but requires even more manipulation + * Cons: requires post-processing (e.g with **flextable**) for publication ready tables. Does not accept multiple variables (need to use **purrr** to iterate). +* Use `count()` and `summarise()` from **dplyr** if preparing data for `ggplot()` or calculating more complex statistics. + * Cons: similar to above but requires even more manipulation. * Use `tbl_summary()` from **gtsummary** to produce detailed publication-ready tables * Cons: missings not automatically included in percentage calculations. To use for plotting need to extract tibble from list output. @@ -145,11 +145,11 @@ Use **janitor**'s "adorn" functions to add totals or convert to proportions, per Function | Outcome -------------------|-------------------------------- `adorn_totals()` | Adds totals (`where = ` "row", "col", or "both"). Set `name =` for "Total". -`adorn_percentages()` | Convert counts to proportions, with `denominator = ` "row", "col", or "all" +`adorn_percentages()` | Convert counts to proportions, with `denominator = ` "row", "col", or "all". `adorn_pct_formatting()` | Converts proportions to percents. Specify `digits =`. Remove "%" with `affix_sign = F`. `adorn_rounding()` | To round counts, proportions to `digits =` places. To round percents use `adorn_pct_formatting()` as above. `adorn_ns()` | Add counts to a table of proportions or percents. Indicate `position =` "rear" for counts in parentheses, or "front" to put the percent in parentheses. -`adorn_title()` | add string `row_name = ` and/or `col_name = ` +`adorn_title()` | add string `row_name = ` and/or `col_name = `. Be conscious of the order you apply the above functions. Below are some examples. @@ -180,7 +180,8 @@ linelist %>% # case linelist adorn_ns(position = "front") %>% # display as: "count (percent)" adorn_title( # adjust titles row_name = "Age Category", - col_name = "Gender") + col_name = "Gender" + ) ``` @@ -199,7 +200,8 @@ linelist %>% adorn_title( row_name = "Age Category", col_name = "Gender", - placement = "combined") %>% # this is necessary to print to HTML + placement = "combined" + ) %>% # this is necessary to print to HTML flextable::flextable() %>% # convert to HTML flextable::autofit() # format to one line per row @@ -223,7 +225,8 @@ linelist %>% adorn_title( row_name = "Age Category", col_name = "Gender", - placement = "combined") %>% + placement = "combined" + ) %>% flextable::flextable() %>% # convert to HTML flextable flextable::autofit() %>% # ensure only one line per row flextable::save_as_docx(path = "tabyl.docx") # save as Word document diff --git a/new_pages/descriptive_statistics_files/figure-html/unnamed-chunk-21-1.png b/new_pages/descriptive_statistics_files/figure-html/unnamed-chunk-21-1.png new file mode 100644 index 00000000..91f21c6d Binary files /dev/null and b/new_pages/descriptive_statistics_files/figure-html/unnamed-chunk-21-1.png differ diff --git a/new_pages/diagrams.qmd b/new_pages/diagrams.qmd index 58729a7f..0089faf0 100644 --- a/new_pages/diagrams.qmd +++ b/new_pages/diagrams.qmd @@ -11,9 +11,9 @@ knitr::include_graphics(here::here("images", "sankey_diagram.png")) This page covers code to produce: -* Flow diagrams using **DiagrammeR** and the DOT language -* Alluvial/Sankey diagrams -* Event timelines +* Flow diagrams using **DiagrammeR** and the DOT language +* Alluvial/Sankey diagrams +* Event timelines @@ -26,18 +26,19 @@ This page covers code to produce: This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. -```{r} +```{r, warning=F, message=F} pacman::p_load( DiagrammeR, # for flow diagrams networkD3, # For alluvial/Sankey diagrams - tidyverse) # data management and visualization + tidyverse # data management and visualization + ) ``` ### Import data {.unnumbered} Most of the content in this page does not require a dataset. However, in the Sankey diagram section, we will use the case linelist from a simulated Ebola epidemic. If you want to follow along for this part, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -67,12 +68,12 @@ The function `grViz()` is used to create a "Graphviz" diagram. This function acc **Basic structure** -1) Open the instructions `grViz("` +1) Open the instructions `grViz("` 2) Specify directionality and name of the graph, and open brackets, e.g. `digraph my_flow_chart {` -3) Graph statement (layout, rank direction) +3) Graph statement (layout, rank direction) 4) Nodes statements (create nodes) -5) Edges statements (gives links between nodes) -6) Close the instructions `}")` +5) Edges statements (gives links between nodes) +6) Close the instructions `}")` ### Simple examples {.unnumbered} @@ -133,7 +134,7 @@ Node names, or edge statements, can be separated with spaces, semicolons, or new **Rank direction** -A plot can be re-oriented to move left-to-right by adjusting the `rankdir` argument within the graph statement. The default is TB (top-to-bottom), but it can be LR (left-to-right), RL, or BT. +A plot can be re-oriented to move left-to-right by adjusting the `rankdir` argument within the graph statement. The default is TB (top-to-bottom), but it can be LR (left-to-right), RL (right-to-left), or BT (bottom-to-top). **Node names** @@ -190,7 +191,7 @@ Within edge statements, subgroups can be created on either side of the edge with * `penwidth` (width of arrow) * `minlen` (minimum length) -**Color names**: hexadecimal values or 'X11' color names, see [here for X11 details](http://rich-iannone.github.io/DiagrammeR/graphviz_and_mermaid.html) +**Color names**: hexadecimal values or 'X11' color names, see [here for X11 details](https://rich-iannone.github.io/DiagrammeR/articles/graphviz-mermaid.html#colors) ### Complex examples {.unnumbered} @@ -198,7 +199,7 @@ Within edge statements, subgroups can be created on either side of the edge with The example below expands on the surveillance_diagram, adding complex node names, grouped edges, colors and styling -``` +```{r, eval = F, echo = T} DiagrammeR::grViz(" # All instructions are within a large character string digraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name @@ -285,7 +286,7 @@ digraph surveillance_diagram { # 'digraph' means 'directional graph', then th To group nodes into boxed clusters, put them within the same named subgraph (`subgraph name {}`). To have each subgraph identified within a bounding box, begin the name of the subgraph with "cluster", as shown with the 4 boxes below. -``` +```{r, eval = F} DiagrammeR::grViz(" # All instructions are within a large character string digraph surveillance_diagram { # 'digraph' means 'directional graph', then the graph name @@ -426,7 +427,7 @@ digraph surveillance_diagram { # 'digraph' means 'directional graph', then the The example below, borrowed from [this tutorial](http://rich-iannone.github.io/DiagrammeR/), shows applied node shapes and a shorthand for serial edge connections ```{r out.width='75%'} -DiagrammeR::grViz("digraph { +saved_plot <- DiagrammeR::grViz("digraph { graph [layout = dot, rankdir = LR] @@ -442,101 +443,54 @@ results [label= 'Results'] # edge definitions with the node IDs {data1 data2} -> process -> statistical -> results }") + +saved_plot ``` ### Outputs {.unnumbered} -How to handle and save outputs - -* Outputs will appear in RStudio's Viewer pane, by default in the lower-right alongside Files, Plots, Packages, and Help. -* To export you can "Save as image" or "Copy to clipboard" from the Viewer. The graphic will adjust to the specified size. - +How to handle and save outputs: +* Outputs will appear in RStudio’s Viewer pane, by default in the lower-right alongside Files, Plots, Packages, and Help. +* To export you can “Save as image” or “Copy to clipboard” from the Viewer. The graphic will adjust to the specified size. ### Parameterized figures {.unnumbered} -Here is a quote from this tutorial: https://mikeyharper.uk/flowcharts-in-r-using-diagrammer/ +Here is a quote from this tutorial, [Data-driven flowcharts in R using DiagrammeR](https://mikeyharper.uk/flowcharts-in-r-using-diagrammer/). "Parameterized figures: A great benefit of designing figures within R is that we are able to connect the figures directly with our analysis by reading R values directly into our flowcharts. For example, suppose you have created a filtering process which removes values after each stage of a process, you can have a figure show the number of values left in the dataset after each stage of your process. To do this we, you can use the @@X symbol directly within the figure, then refer to this in the footer of the plot using [X]:, where X is the a unique numeric index." -We encourage you to review this tutorial if parameterization is something you are interested in. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +We encourage you to review [this tutorial](https://mikeyharper.uk/flowcharts-in-r-using-diagrammer/) if parameterization is something you are interested in. - - ## Alluvial/Sankey Diagrams { } ### Load packages {.unnumbered} This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. -We load the **networkD3** package to produce the diagram, and also **tidyverse** for the data preparation steps. +We load the **ggalluvial** package to produce the diagram, and also **tidyverse** for the data preparation steps. ```{r} pacman::p_load( - networkD3, - tidyverse) + ggalluvial, # For alluvial/Sankey diagrams + tidyverse # data management and visualization + ) ``` ### Plotting from dataset {.unnumbered} -Plotting the connections in a dataset. Below we demonstrate using this package on the case `linelist`. Here is an [online tutorial](https://www.r-graph-gallery.com/321-introduction-to-interactive-sankey-diagram-2.html). +Plotting the connections in a dataset. Below we demonstrate using this package on the case `linelist`. Here is an [online tutorial](https://cran.r-project.org/web/packages/ggalluvial/vignettes/ggalluvial.html). -We begin by getting the case counts for each unique age category and hospital combination. We've removed values with missing age category for clarity. We also re-label the `hospital` and `age_cat` columns as `source` and `target` respectively. These will be the two sides of the alluvial diagram. +We begin by getting the case counts for each unique `gender`, `source`, `vomit` and `outcome` combination. We've removed missing values for clarity. ```{r} # counts by hospital and age category links <- linelist %>% - drop_na(age_cat) %>% - select(hospital, age_cat) %>% - count(hospital, age_cat) %>% - rename(source = hospital, - target = age_cat) + select(gender, source, vomit, outcome) %>% + count(gender, source, vomit, outcome) %>% + drop_na() #It is not necessary to drop NA values, we are just doing this for display purposes ``` The dataset now look like this: @@ -545,119 +499,62 @@ The dataset now look like this: DT::datatable(links, rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap') ``` +Now plot the Sankey diagram with `geom_alluvium()` and `geom_stratum()`. You can read more about each argument by running `?geom_alluvium` and `?geom_stratum` in the console. -Now we create a data frame of all the diagram nodes, under the column `name`. This consists of all the values for `hospital` and `age_cat`. Note that we ensure they are all class Character before combining them. and adjust the ID columns to be numbers instead of labels: +In this plot we will look at `source`, `vomit` and colour our flows by `gender`. ```{r} -# The unique node names -nodes <- data.frame( - name=c(as.character(links$source), as.character(links$target)) %>% - unique() - ) - -nodes # print -``` -The we edit the `links` data frame, which we created above with `count()`. We add two numeric columns `IDsource` and `IDtarget` which will actually reflect/create the links between the nodes. These columns will hold the rownumbers (position) of the source and target nodes. 1 is subtracted so that these position numbers begin at 0 (not 1). -```{r} -# match to numbers, not names -links$IDsource <- match(links$source, nodes$name)-1 -links$IDtarget <- match(links$target, nodes$name)-1 -``` +# plot +ggplot(data = links, + mapping = aes(y = n, + axis1 = source, + axis2 = vomit)) + + geom_alluvium(aes(fill = gender)) + + geom_stratum(width = 1/12, fill = "black", color = "grey") + + geom_label(stat = "stratum", aes(label = after_stat(stratum))) + + scale_x_discrete(limits = c("Hospital", "Age category"), expand = c(.05, .05)) + + scale_fill_brewer(type = "qual", palette = "Set1") + + theme_void() -The links dataset now looks like this: -```{r message=FALSE, echo=F} -DT::datatable(links, rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap') ``` -Now plot the Sankey diagram with `sankeyNetwork()`. You can read more about each argument by running `?sankeyNetwork` in the console. Note that unless you set `iterations = 0` the order of your nodes may not be as expected. +To add an additional axis, we can put in the argument `axis3` within `mapping = aes()`. If we would like to move the plot from horizontal to vertical, we can use the argument `coord_flip()`. +Here is an example where the `outcome` is included as well in the argument `axis3 = outcome` ```{r} # plot -###### -p <- sankeyNetwork( - Links = links, - Nodes = nodes, - Source = "IDsource", - Target = "IDtarget", - Value = "n", - NodeID = "name", - units = "TWh", - fontSize = 12, - nodeWidth = 30, - iterations = 0) # ensure node order is as in data -p -``` - - - -Here is an example where the patient Outcome is included as well. Note in the data preparation step we have to calculate the counts of cases between age and hospital, and separately between hospital and outcome - and then bind all these counts together with `bind_rows()`. - -```{r} -# counts by hospital and age category -age_hosp_links <- linelist %>% - drop_na(age_cat) %>% - select(hospital, age_cat) %>% - count(hospital, age_cat) %>% - rename(source = age_cat, # re-name - target = hospital) - -hosp_out_links <- linelist %>% - drop_na(age_cat) %>% - select(hospital, outcome) %>% - count(hospital, outcome) %>% - rename(source = hospital, # re-name - target = outcome) - -# combine links -links <- bind_rows(age_hosp_links, hosp_out_links) - -# The unique node names -nodes <- data.frame( - name=c(as.character(links$source), as.character(links$target)) %>% - unique() - ) - -# Create id numbers -links$IDsource <- match(links$source, nodes$name)-1 -links$IDtarget <- match(links$target, nodes$name)-1 - -# plot -###### -p <- sankeyNetwork(Links = links, - Nodes = nodes, - Source = "IDsource", - Target = "IDtarget", - Value = "n", - NodeID = "name", - units = "TWh", - fontSize = 12, - nodeWidth = 30, - iterations = 0) -p +ggplot(data = links, + mapping = aes(y = n, + axis1 = source, + axis2 = vomit, + axis3 = outcome)) + + geom_alluvium(aes(fill = gender)) + + geom_stratum(width = 1/12, fill = "black", color = "grey") + + geom_label(stat = "stratum", aes(label = after_stat(stratum))) + + scale_x_discrete(limits = c("Hospital", "Age category"), expand = c(.05, .05)) + + scale_fill_brewer(type = "qual", palette = "Set1") + + theme_void() + + coord_flip() ``` - -https://www.displayr.com/sankey-diagrams-r/ - - - ## Event timelines { } -To make a timeline showing specific events, you can use the `vistime` package. +To make a timeline showing specific events, you can use the **vistime** package. See this [vignette](https://cran.r-project.org/web/packages/vistime/vignettes/vistime-vignette.html#ex.-2-project-planning) ```{r} # load package -pacman::p_load(vistime, # make the timeline - plotly # for interactive visualization - ) +pacman::p_load( + vistime, # make the timeline + plotly # for interactive visualization + ) ``` ```{r, echo=F} @@ -690,9 +587,8 @@ DT::datatable(data, rownames = FALSE, filter="top", options = list(pageLength = ```{r} -p <- vistime(data) # apply vistime -library(plotly) +p <- vistime(data) # apply vistime # step 1: transform into a list pp <- plotly_build(p) @@ -738,14 +634,11 @@ Alternatively, there are packages like **ggdag** and **daggity** ## Resources { } - - Much of the above regarding the DOT language is adapted from the tutorial [at this site](https://mikeyharper.uk/flowcharts-in-r-using-diagrammer/) Another more in-depth [tutorial on DiagammeR](http://rich-iannone.github.io/DiagrammeR/) -This page on [Sankey diagrams](https://www.displayr.com/sankey-diagrams-r/ -) +This page on [Sankey diagrams](http://corybrunson.github.io/ggalluvial/articles/ggalluvial.html) diff --git a/new_pages/directories.qmd b/new_pages/directories.qmd index 416c58e4..e1e8e844 100644 --- a/new_pages/directories.qmd +++ b/new_pages/directories.qmd @@ -15,7 +15,8 @@ pacman::p_load( fs, # file/directory interactions rio, # import/export here, # relative file pathways - tidyverse) # data management and visualization + tidyverse # data management and visualization + ) ``` @@ -149,12 +150,6 @@ file_create(here("data", "test.rds")) A **base** R alternative is `file.create()`. But if the file already exists, this option will truncate it. If you use `file_create()` the file will be left unchanged. - -### Create if does not exists {.unnumbered} - -UNDER CONSTRUCTION - - ## Delete ### R objects {.unnumbered} @@ -184,7 +179,7 @@ source(here("scripts", "cleaning_scripts", "clean_testing_data.R")) This is equivalent to viewing the above R script and clicking the "Source" button in the upper-right of the script. This will execute the script but will do it silently (no output to the R console) unless specifically intended. See the page on [Interactive console] for examples of using `source()` to interact with a user via the R console in question-and-answer mode. -```{r, fig.align = "center", out.height = '300%', echo=F} +```{r, fig.align = "center", out.width = '300%', out.height = '300%', echo=F} knitr::include_graphics(here::here("images", "source_button.png")) ``` @@ -235,10 +230,10 @@ Also see the [Import and export](importing.qmd) page for methods to automaticall See the page on [Iteration, loops, and lists] for an example with the package **purrr** demonstrating: -* Splitting a data frame and saving it out as multiple CSV files -* Splitting a data frame and saving each part as a separate sheet within one Excel workbook -* Importing multiple CSV files and combining them into one dataframe -* Importing an Excel workbook with multiple sheets and combining them into one dataframe +* Splitting a data frame and saving it out as multiple CSV files. +* Splitting a data frame and saving each part as a separate sheet within one Excel workbook. +* Importing multiple CSV files and combining them into one data frame. +* Importing an Excel workbook with multiple sheets and combining them into one data frame. @@ -263,7 +258,7 @@ If a file is currently "open", it will display in your folder with a tilde in fr ## Resources { } -https://cran.r-project.org/web/packages/fs/vignettes/function-comparisons.html +[Comparison of fs functions, base R and shell commands](https://cran.r-project.org/web/packages/fs/vignettes/function-comparisons.html) diff --git a/new_pages/editorial_style.qmd b/new_pages/editorial_style.qmd index 888cdfa0..4e1bd2a2 100644 --- a/new_pages/editorial_style.qmd +++ b/new_pages/editorial_style.qmd @@ -1,6 +1,6 @@ # Editorial and technical notes {#editorial-style} -In this page we describe the philosophical approach, style, and specific editorial decisions made during the creation of this handbook. +In this page we describe the philosophical approach, style, and specific editorial decisions made during the creation of this handbook. ## Approach and style @@ -9,9 +9,9 @@ The potential audience for this book is large. It will surely be used by people A few other points: -* This is a code reference book accompanied by relatively brief examples - *not* a thorough textbook on R or data science -* This is a *R handbook* for use within applied epidemiology - not a manual on the methods or science of applied epidemiology -* This is intended to be a living document - optimal R packages for a given task change often and we welcome discussion about which to emphasize in this handbook +* This is a code reference book accompanied by relatively brief examples - *not* a thorough textbook on R or data science. +* This is a *R handbook* for use within applied epidemiology - not a manual on the methods or science of applied epidemiology. +* This is intended to be a living document - optimal R packages for a given task change often and we welcome discussion about which to emphasize in this handbook. @@ -22,9 +22,9 @@ A few other points: One of the most challenging aspects of learning R is knowing which R package to use for a given task. It is a common occurrence to struggle through a task only later to realize - hey, there's an R package that does all that in one command line! -In this handbook, we try to offer you at least two ways to complete each task: one tried-and-true method (probably in **base** R or **tidyverse**) and one special R package that is custom-built for that purpose. We want you to have a couple options in case you can't download a given package or it otherwise does not work for you. +In this handbook, we try to offer you at least two ways to complete each task: one tried-and-true method (probably in **base** R or **tidyverse**) and one special R package that is custom-built for that purpose. We want you to have a couple of options in case you can't download a given package or it otherwise does not work for you. -In choosing which packages to use, we prioritized R packages and approaches that have been tested and vetted by the community, minimize the number of packages used in a typical work session, that are stable (not changing very often), and that accomplish the task simply and cleanly +In choosing which packages to use, we prioritized R packages and approaches that have been tested and vetted by the community, minimized the number of packages used in a typical work session, focused on packages that are stable (not changing very often), and that accomplish the task simply and cleanly. This handbook generally prioritizes R packages and functions from the **tidyverse**. Tidyverse is a collection of R packages designed for data science that share underlying grammar and data structures. All tidyverse packages can be installed or loaded via the **tidyverse** package. Read more at the [tidyverse website](https://www.tidyverse.org/). @@ -43,10 +43,10 @@ See the page on [R basics](basics.qmd) to learn more about packages and function In the handbook, we frequently utilize "new lines", making our code appear "long". We do this for a few reasons: -* We can write explanatory comments with `#` that are adjacent to each little part of the code -* Generally, longer (vertical) code is easier to read -* It is easier to read on a narrow screen (no sideways scrolling needed) -* From the indentations, it can be easier to know which arguments belong to which function +* We can write explanatory comments with `#` that are adjacent to each little part of the code. +* Generally, longer (vertical) code is easier to read. +* It is easier to read on a narrow screen (no sideways scrolling needed). +* From the indentations, it can be easier to know which arguments belong to which function. As a result, code that *could* be written like this: @@ -77,7 +77,7 @@ We also use lots of spaces (e.g. `n = 1` instead of `n=1`) because it is easier In this handbook, we generally reference "columns" and "rows" instead of "variables" and "observations". As explained in this primer on ["tidy data"](https://tidyr.tidyverse.org/articles/tidy-data.html), most epidemiological statistical datasets consist structurally of rows, columns, and values. -*Variables* contain the values that measure the same underlying attribute (like age group, outcome, or date of onset). *Observations* contain all values measured on the same unit (e.g. a person, site, or lab sample). So these aspects can be more difficult to tangibly define. +*Variables* contain the values that measure the same underlying attribute (like age group, outcome, or date of onset). *Observations* contain all values measured on the same unit (e.g. a person, site, or lab sample). These aspects can be more difficult to tangibly define. In "tidy" datasets, each column is a variable, each row is an observation, and each cell is a single value. However some datasets you encounter will not fit this mold - a "wide" format dataset may have a variable split across several columns (see an example in the [Pivoting data](#pivoting) page). Likewise, observations could be split across several rows. @@ -130,16 +130,16 @@ Date |Major changes **NEWS** With version 1.0.1 the following changes have been implemented: -* Update to R version 4.2 -* Data cleaning: switched {linelist} to {matchmaker}, removed unnecessary line from `case_when()` example -* Dates: switched {linelist} `guess_date()` to {parsedate} `parse_date()` -* Pivoting: slight update to `pivot_wider()` `id_cols=` -* Survey analysis: switched `plot_age_pyramid()` to `age_pyramid()`, slight change to alluvial plot code -* Heat plots: added `ungroup()` to `agg_weeks` chunk -* Interactive plots: added `ungroup()` to chunk that makes `agg_weeks` so that `expand()` works as intended -* Time series: added `data.frame()` around objects within all `trending::fit()` and `predict()` commands -* Combinations analysis: Switch `case_when()` to `ifelse()` and added optional `across()` code for preparing the data -* Transmission chains: Update to more recent version of {epicontacts} +* Update to R version 4.2. +* Data cleaning: switched {linelist} to {matchmaker}, removed unnecessary line from `case_when()` example. +* Dates: switched **linelist** `guess_date()` to **parsedate** `parse_date()`. +* Pivoting: slight update to `pivot_wider()` `id_cols=`. +* Survey analysis: switched `plot_age_pyramid()` to `age_pyramid()`, slight change to alluvial plot code. +* Heat plots: added `ungroup()` to `agg_weeks` chunk. +* Interactive plots: added `ungroup()` to chunk that makes `agg_weeks` so that `expand()` works as intended. +* Time series: added `data.frame()` around objects within all `trending::fit()` and `predict()` commands. +* Combinations analysis: Switch `case_when()` to `ifelse()` and added optional `across()` code for preparing the data. +* Transmission chains: Update to more recent version of [**epicontacts**](https://www.repidemicsconsortium.org/epicontacts/). diff --git a/new_pages/epicurves.qmd b/new_pages/epicurves.qmd index 4b06b9fe..7612510d 100644 --- a/new_pages/epicurves.qmd +++ b/new_pages/epicurves.qmd @@ -10,17 +10,15 @@ An epidemic curve (also known as an "epi curve") is a core epidemiological chart Analysis of the epicurve can reveal temporal trends, outliers, the magnitude of the outbreak, the most likely time period of exposure, time intervals between case generations, and can even help identify the mode of transmission of an unidentified disease (e.g. point source, continuous common source, person-to-person propagation). One online lesson on interpretation of epi curves can be found at the website of the [US CDC](https://www.cdc.gov/training/quicklearns/epimode/index.html). -In this page we demonstrate making epidemic curves with the **ggplot2** package, which allows for advanced customizability. +In this page we demonstrate making epidemic curves with the **ggplot2** package, which allows for advanced customizability, and **incidence2** which allows for rapid creation of epidemic curves with pre-specified functions. Also addressed are specific use-cases such as: -* Plotting aggregated count data -* Faceting or producing small-multiples -* Applying moving averages -* Showing which data are "tentative" or subject to reporting delays -* Overlaying cumulative case incidence using a second axis - -The **incidence2** package offers alternative approach with easier commands, but as of this writing was undergoing revisions and was not stable. It will be re-added to this chapter when stable. +* Plotting aggregated count data. +* Faceting or producing small-multiples. +* Applying moving averages. +* Showing which data are "tentative" or subject to reporting delays. +* Overlaying cumulative case incidence using a second axis. @@ -34,6 +32,7 @@ This code chunk shows the loading of packages required for the analyses. In this ```{r message=F, warning=F} pacman::p_load( rio, # file import/export + tidyquant, # for moving averages here, # relative filepaths lubridate, # working with dates/epiweeks aweek, # alternative package for working with dates/epiweeks @@ -41,6 +40,7 @@ pacman::p_load( i2extras, # supplement to incidence2 stringr, # search and manipulate character strings forcats, # working with factors + cowplot, # for dual axes RColorBrewer, # Color palettes from colorbrewer2.org tidyverse # data management + ggplot2 graphics ) @@ -51,8 +51,8 @@ pacman::p_load( Two example datasets are used in this section: -* Linelist of individual cases from a simulated epidemic -* Aggregated counts by hospital from the same simulated epidemic +* Linelist of individual cases from a simulated epidemic. +* Aggregated counts by hospital from the same simulated epidemic. The datasets are imported using the `import()` function from the **rio** package. See the page on [Import and export](importing.qmd) for various ways to import data. @@ -128,7 +128,7 @@ Verify that each relevant date column is class Date and has an appropriate range ```{r, out.width = c('50%', '50%', '50%'), fig.show='hold', warning=F, message=F} # check range of onset dates -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_histogram(aes(x = date_onset)) ``` @@ -161,9 +161,9 @@ library(tidyverse) To produce an epicurve with `ggplot()` there are three main elements: -* A histogram, with linelist cases aggregated into "bins" distinguished by specific "break" points -* Scales for the axes and their labels -* Themes for the plot appearance, including titles, labels, captions, etc. +* A histogram, with linelist cases aggregated into "bins" distinguished by specific "break" points +* Scales for the axes and their labels +* Themes for the plot appearance, including titles, labels, captions, etc ### Specify case bins {.unnumbered} @@ -182,14 +182,14 @@ In the over-arching `ggplot()` command the dataset is provided to `data = `. Ont ggplot(data = central_data) + # set data geom_histogram( # add histogram mapping = aes(x = date_onset), # map date column to x-axis - binwidth = 1)+ # cases binned by 1 day + binwidth = 1) + # cases binned by 1 day labs(title = "Central Hospital - Daily") # title # weekly ggplot(data = central_data) + # set data geom_histogram( # add histogram mapping = aes(x = date_onset), # map date column to x-axis - binwidth = 7)+ # cases binned every 7 days, starting from first case (!) + binwidth = 7) + # cases binned every 7 days, starting from first case (!) labs(title = "Central Hospital - 7-day bins, starting at first case") # title ``` @@ -218,7 +218,7 @@ This vector can be provided to `geom_histogram()` as `breaks = `: ggplot(data = central_data) + geom_histogram( mapping = aes(x = date_onset), - breaks = monthly_breaks)+ # provide the pre-defined vector of breaks + breaks = monthly_breaks) + # provide the pre-defined vector of breaks labs(title = "Monthly case bins") # title ``` @@ -236,17 +236,17 @@ An alternative to supplying specific start and end dates is to write *dynamic* c ```{r} # Sequence of weekly Monday dates for CENTRAL HOSPITAL weekly_breaks_central <- seq.Date( - from = floor_date(min(central_data$date_onset, na.rm=T), "week", week_start = 1), # monday before first case - to = ceiling_date(max(central_data$date_onset, na.rm=T), "week", week_start = 1), # monday after last case + from = floor_date(min(central_data$date_onset, na.rm = T), "week", week_start = 1), # monday before first case + to = ceiling_date(max(central_data$date_onset, na.rm = T), "week", week_start = 1), # monday after last case by = "week") ``` Let's unpack the rather daunting code above: -* The "from" value (earliest date of the sequence) is created as follows: the minimum date value (`min()` with `na.rm=TRUE`) in the column `date_onset` is fed to `floor_date()` from the **lubridate** package. `floor_date()` set to "week" returns the start date of that cases's "week", given that the start day of each week is a Monday (`week_start = 1`). -* Likewise, the "to" value (end date of the sequence) is created using the inverse function `ceiling_date()` to return the Monday *after* the last case. -* The "by" argument of `seq.Date()` can be set to any number of days, weeks, or months. -* Use `week_start = 7` for Sunday weeks +* The "from" value (earliest date of the sequence) is created as follows: the minimum date value (`min()` with `na.rm=TRUE`) in the column `date_onset` is fed to `floor_date()` from the **lubridate** package. `floor_date()` set to "week" returns the start date of that cases's "week", given that the start day of each week is a Monday (`week_start = 1`) +* Likewise, the "to" value (end date of the sequence) is created using the inverse function `ceiling_date()` to return the Monday *after* the last case. +* The "by" argument of `seq.Date()` can be set to any number of days, weeks, or months +* Use `week_start = 7` for Sunday weeks As we will use these date vectors throughout this page, we also define one for the whole outbreak (the above is for Central Hospital only). @@ -269,19 +269,19 @@ These `seq.Date()` outputs can be used to create histogram bin breaks, but also **Below is detailed example code to produce weekly epicurves for Monday weeks, with aligned bars, date labels, and vertical gridlines.** This section is for the user who needs code quickly. To understand each aspect (themes, date labels, etc.) in-depth, continue to the subsequent sections. Of note: -* The *histogram bin breaks* are defined with `seq.Date()` as explained above to begin the Monday before the earliest case and to end the Monday after the last case -* The interval of *date labels* is specified by `date_breaks =` within `scale_x_date()` -* The interval of minor vertical gridlines between date labels is specified to `date_minor_breaks = ` -* We use `closed = "left"` in the `geom_histogram()` to ensure the date are counted in the correct bins -* `expand = c(0,0)` in the x and y scales removes excess space on each side of the axes, which also ensures the date labels begin from the first bar. +* The *histogram bin breaks* are defined with `seq.Date()` as explained above to begin the Monday before the earliest case and to end the Monday after the last case +* The interval of *date labels* is specified by `date_breaks =` within `scale_x_date()` +* The interval of minor vertical gridlines between date labels is specified to `date_minor_breaks = ` +* We use `closed = "left"` in the `geom_histogram()` to ensure the date are counted in the correct bins +* `expand = c(0,0)` in the x and y scales removes excess space on each side of the axes, which also ensures the date labels begin from the first bar ```{r, warning=F, message=F} # TOTAL MONDAY WEEK ALIGNMENT ############################# # Define sequence of weekly breaks weekly_breaks_central <- seq.Date( - from = floor_date(min(central_data$date_onset, na.rm=T), "week", week_start = 1), # Monday before first case - to = ceiling_date(max(central_data$date_onset, na.rm=T), "week", week_start = 1), # Monday after last case + from = floor_date(min(central_data$date_onset, na.rm = T), "week", week_start = 1), # Monday before first case + to = ceiling_date(max(central_data$date_onset, na.rm = T), "week", week_start = 1), # Monday after last case by = "week") # bins are 7-days @@ -301,26 +301,26 @@ ggplot(data = central_data) + # bars color = "darkblue", # color of lines around bars fill = "lightblue" # color of fill within bars - )+ + ) + # x-axis labels scale_x_date( expand = c(0,0), # remove excess x-axis space before and after case bars date_breaks = "4 weeks", # date labels and major vertical gridlines appear every 3 Monday weeks date_minor_breaks = "week", # minor vertical lines appear every Monday week - date_labels = "%a\n%d %b\n%Y")+ # date labels format + date_labels = "%a\n%d %b\n%Y") + # date labels format # y-axis scale_y_continuous( - expand = c(0,0))+ # remove excess y-axis space below 0 (align histogram flush with x-axis) + expand = c(0,0)) + # remove excess y-axis space below 0 (align histogram flush with x-axis) # aesthetic themes - theme_minimal()+ # simplify plot background + theme_minimal() + # simplify plot background theme( plot.caption = element_text(hjust = 0, # caption on left side face = "italic"), # caption in italics - axis.title = element_text(face = "bold"))+ # axis titles in bold + axis.title = element_text(face = "bold")) + # axis titles in bold # labels including dynamic caption labs( @@ -336,8 +336,8 @@ ggplot(data = central_data) + To achieve the above plot for Sunday weeks a few modifications are needed, because the `date_breaks = "weeks"` only work for Monday weeks. -* The break points of the *histogram bins* must be set to Sundays (`week_start = 7`) -* Within `scale_x_date()`, the similar date breaks should be provided to `breaks =` and `minor_breaks = ` to ensure the date labels and vertical gridlines align on Sundays. +* The break points of the *histogram bins* must be set to Sundays (`week_start = 7`) +* Within `scale_x_date()`, the similar date breaks should be provided to `breaks =` and `minor_breaks = ` to ensure the date labels and vertical gridlines align on Sundays For example, the `scale_x_date()` command for Sunday weeks could look like this: @@ -347,19 +347,19 @@ scale_x_date( # specify interval of date labels and major vertical gridlines breaks = seq.Date( - from = floor_date(min(central_data$date_onset, na.rm=T), "week", week_start = 7), # Sunday before first case - to = ceiling_date(max(central_data$date_onset, na.rm=T), "week", week_start = 7), # Sunday after last case + from = floor_date(min(central_data$date_onset, na.rm = T), "week", week_start = 7), # Sunday before first case + to = ceiling_date(max(central_data$date_onset, na.rm = T), "week", week_start = 7), # Sunday after last case by = "4 weeks"), # specify interval of minor vertical gridline minor_breaks = seq.Date( - from = floor_date(min(central_data$date_onset, na.rm=T), "week", week_start = 7), # Sunday before first case - to = ceiling_date(max(central_data$date_onset, na.rm=T), "week", week_start = 7), # Sunday after last case + from = floor_date(min(central_data$date_onset, na.rm = T), "week", week_start = 7), # Sunday before first case + to = ceiling_date(max(central_data$date_onset, na.rm = T), "week", week_start = 7), # Sunday after last case by = "week"), # date label format - #date_labels = "%a\n%d %b\n%Y")+ # day, above month abbrev., above 2-digit year - label = scales::label_date_short())+ # automatic label formatting + #date_labels = "%a\n%d %b\n%Y") + # day, above month abbrev., above 2-digit year + label = scales::label_date_short()) + # automatic label formatting ``` @@ -370,8 +370,8 @@ scale_x_date( The histogram bars can be colored by group and "stacked". To designate the grouping column, make the following changes. See the [ggplot basics](ggplot_basics.qmd) page for details. * Within the histogram aesthetic mapping `aes()`, map the column name to the `group = ` and `fill = ` arguments -* Remove any `fill = ` argument *outside* of `aes()`, as it will override the one inside -* Arguments *inside* `aes()` will apply *by group*, whereas any *outside* will apply to all bars (e.g. you may still want `color = ` outside, so each bar has the same border) +* Remove any `fill = ` argument *outside* of `aes()`, as it will override the one inside +* Arguments *inside* `aes()` will apply *by group*, whereas any *outside* will apply to all bars (e.g. you may still want `color = ` outside, so each bar has the same border) Here is what the `aes()` command would look like to group and color the bars by gender: @@ -403,15 +403,15 @@ ggplot(data = linelist) + # begin with linelist (many hospitals) ### Adjust colors {.unnumbered} -* To *manually* set the fill for each group, use `scale_fill_manual()` (note: `scale_color_manual()` is different!). - * Use the `values = ` argument to apply a vector of colors. - * Use `na.value = ` to specify a color for `NA` values. - * Use the `labels = ` argument to change the text of legend items. To be safe, provide as a named vector like `c("old" = "new", "old" = "new")` or adjust the values in the data itself. - * Use `name = ` to give a proper title to the legend -* For more tips on color scales and palettes, see the page on [ggplot basics](ggplot_basics.qmd). +* To *manually* set the fill for each group, use `scale_fill_manual()` (note: `scale_color_manual()` is different!) + * Use the `values = ` argument to apply a vector of colors + * Use `na.value = ` to specify a color for `NA` values + * Use the `labels = ` argument to change the text of legend items. To be safe, provide as a named vector like `c("old" = "new", "old" = "new")` or adjust the values in the data itself + * Use `name = ` to give a proper title to the legend +* For more tips on color scales and palettes, see the page on [ggplot basics](ggplot_basics.qmd) ```{r, warning=F, message=F} -ggplot(data = linelist)+ # begin with linelist (many hospitals) +ggplot(data = linelist) + # begin with linelist (many hospitals) # make histogram geom_histogram( @@ -425,7 +425,7 @@ ggplot(data = linelist)+ # begin with linelist (many hospitals) closed = "left", # count cases from start of breakpoint # Color around bars - color = "black")+ # border color of each bar + color = "black") + # border color of each bar # manual specification of colors scale_fill_manual( @@ -469,7 +469,7 @@ ggplot(plot_data) + # Use NEW dataset with hospital as re-or closed = "left", # count cases from start of breakpoint - color = "black")+ # border color around each bar + color = "black") + # border color around each bar # x-axis labels scale_x_date( @@ -480,21 +480,21 @@ ggplot(plot_data) + # Use NEW dataset with hospital as re-or # y-axis scale_y_continuous( - expand = c(0,0))+ # remove excess y-axis space below 0 + expand = c(0,0)) + # remove excess y-axis space below 0 # manual specification of colors, ! attention to order scale_fill_manual( values = c("grey", "beige", "black", "orange", "blue", "brown"), labels = c("St. Mark's Maternity Hospital (SMMH)" = "St. Mark's"), - name = "Hospital")+ + name = "Hospital") + # aesthetic themes - theme_minimal()+ # simplify plot background + theme_minimal() + # simplify plot background theme( plot.caption = element_text(face = "italic", # caption on left side in italics hjust = 0), - axis.title = element_text(face = "bold"))+ # axis titles in bold + axis.title = element_text(face = "bold")) + # axis titles in bold # labels labs( @@ -515,9 +515,9 @@ ggplot(plot_data) + # Use NEW dataset with hospital as re-or Read more about legends and scales in the [ggplot tips](ggplot_tips.qmd) page. Here are a few highlights: * Edit legend title either in the scale function or with `labs(fill = "Legend title")` (if your are using `color = ` aesthetic, then use `labs(color = "")`) -* `theme(legend.title = element_blank())` to have no legend title -* `theme(legend.position = "top")` ("bottom", "left", "right", or "none" to remove the legend) -* `theme(legend.direction = "horizontal")` horizontal legend +* `theme(legendtitle = element_blank())` to have no legend title +* `theme(legendposition = "top")` ("bottom", "left", "right", or "none" to remove the legend) +* `theme(legenddirection = "horizontal")` horizontal legend * `guides(fill = guide_legend(reverse = TRUE))` to reverse order of the legend @@ -533,7 +533,8 @@ Side-by-side display of group bars (as opposed to stacked) is specified within ` If there are more than two value groups, these can become difficult to read. Consider instead using a faceted plot (small multiples). To improve readability in this example, missing gender values are removed. ```{r, warning=F, message=F} -ggplot(central_data %>% drop_na(gender))+ # begin with Central Hospital cases dropping missing gender +ggplot(central_data %>% + drop_na(gender)) + # begin with Central Hospital cases dropping missing gender geom_histogram( mapping = aes( x = date_onset, @@ -547,26 +548,26 @@ ggplot(central_data %>% drop_na(gender))+ # begin with Central Hospital cases color = "black", # bar edge color - position = "dodge")+ # SIDE-BY-SIDE bars + position = "dodge") + # SIDE-BY-SIDE bars # The labels on the x-axis scale_x_date(expand = c(0,0), # remove excess x-axis space below and after case bars date_breaks = "3 weeks", # labels appear every 3 Monday weeks date_minor_breaks = "week", # vertical lines appear every Monday week - label = scales::label_date_short())+ # efficient date labels + label = scales::label_date_short()) + # efficient date labels # y-axis - scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space between bottom of bars and the labels + scale_y_continuous(expand = c(0,0)) + # removes excess y-axis space between bottom of bars and the labels #scale of colors and legend labels scale_fill_manual(values = c("brown", "orange"), # specify fill colors ("values") - attention to order! - na.value = "grey" )+ + na.value = "grey" ) + # aesthetic themes - theme_minimal()+ # a set of themes to simplify plot + theme_minimal() + # a set of themes to simplify plot theme(plot.caption = element_text(face = "italic", hjust = 0), # caption on left side in italics - axis.title = element_text(face = "bold"))+ # axis titles in bold + axis.title = element_text(face = "bold")) + # axis titles in bold # labels labs(title = "Weekly incidence of cases, by gender", @@ -608,7 +609,7 @@ scale_x_date(limits = c(NA, Sys.Date()) # ensures date axis will extend until cu To **modify the date labels and grid lines**, use `scale_x_date()` in one of these ways: * **If your histogram bins are days, Monday weeks, months, or years**: - * Use `date_breaks = ` to specify the interval of labels and major gridlines (e.g. "day", "week", "3 weeks", "month", or "year") + * Use `date_breaks = ` to specify the interval of labels and major gridlines (eg "day", "week", "3 weeks", "month", or "year") * Use `date_minor_breaks = ` to specify interval of minor vertical gridlines (between date labels) * Add `expand = c(0,0)` to begin the labels at the first bar * Use `date_labels = ` to specify format of date labels - see the Dates page for tips (use `\n` for a new line) @@ -618,8 +619,8 @@ To **modify the date labels and grid lines**, use `scale_x_date()` in one of the Some notes: -* See the opening ggplot section for instructions on how to create a sequence of dates using `seq.Date()`. -* See [this page](https://rdrr.io/r/base/strptime.html) or the [Working with dates](dates.qmd) page for tips on creating date labels. +* See the opening ggplot section for instructions on how to create a sequence of dates using `seqDate()` +* See [this page](https://rdrrio/r/base/strptimehtml) or the [Working with dates](datesqmd) page for tips on creating date labels @@ -639,13 +640,13 @@ ggplot(central_data) + fill = "lightblue") + scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "3 weeks", # Monday every 3 weeks date_minor_breaks = "week", # Monday weeks - label = scales::label_date_short())+ # automatic label formatting + label = scales::label_date_short()) + # automatic label formatting scale_y_continuous( - expand = c(0,0))+ # remove excess space under x-axis, make flush + expand = c(0, 0)) + # remove excess space under x-axis, make flush labs( title = "MISALIGNED", @@ -663,13 +664,13 @@ ggplot(central_data) + fill = "lightblue") + scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "months", # 1st of month date_minor_breaks = "week", # Monday weeks - label = scales::label_date_short())+ # automatic label formatting + label = scales::label_date_short()) + # automatic label formatting scale_y_continuous( - expand = c(0,0))+ # remove excess space under x-axis, make flush + expand = c(0, 0)) + # remove excess space under x-axis, make flush labs( title = "MISALIGNED", @@ -692,13 +693,13 @@ ggplot(central_data) + fill = "lightblue") + scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "4 weeks", # Monday every 4 weeks date_minor_breaks = "week", # Monday weeks - label = scales::label_date_short())+ # label formatting + label = scales::label_date_short()) + # label formatting scale_y_continuous( - expand = c(0,0))+ # remove excess space under x-axis, make flush + expand = c(0, 0)) + # remove excess space under x-axis, make flush labs( title = "ALIGNED Mondays", @@ -721,15 +722,15 @@ ggplot(central_data) + fill = "lightblue") + scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "months", # Monday every 4 weeks date_minor_breaks = "week", # Monday weeks - label = scales::label_date_short())+ # label formatting + label = scales::label_date_short()) + # label formatting scale_y_continuous( - expand = c(0,0))+ # remove excess space under x-axis, make flush + expand = c(0, 0)) + # remove excess space under x-axis, make flush - theme(panel.grid.major = element_blank())+ # Remove major gridlines (fall on 1st of month) + theme(panel.grid.major = element_blank()) + # Remove major gridlines (fall on 1st of month) labs( title = "ALIGNED Mondays with MONTHLY labels", @@ -754,7 +755,7 @@ ggplot(central_data) + fill = "lightblue") + scale_x_date( - expand = c(0,0), + expand = c(0, 0), # date label breaks and major gridlines set to every 3 weeks beginning Sunday before first case breaks = seq.Date(from = floor_date(min(central_data$date_onset, na.rm=T), "week", week_start = 7), to = ceiling_date(max(central_data$date_onset, na.rm=T), "week", week_start = 7), @@ -765,10 +766,10 @@ ggplot(central_data) + to = ceiling_date(max(central_data$date_onset, na.rm=T), "week", week_start = 7), by = "7 days"), - label = scales::label_date_short())+ # label formatting + label = scales::label_date_short()) + # label formatting scale_y_continuous( - expand = c(0,0))+ # remove excess space under x-axis, make flush + expand = c(0, 0)) + # remove excess space under x-axis, make flush labs(title = "ALIGNED Sundays", subtitle = "7-day bins manually set to begin Sunday before first case (27 Apr)\nDate labels and gridlines manually set to Sundays as well") @@ -795,15 +796,16 @@ We can plot a daily epicurve from these *daily counts*. Here are the differences * Within the aesthetic mapping `aes()`, specify `y = ` as the counts column (in this case, the column name is `n_cases`) * Add the argument `stat = "identity"` within `geom_histogram()`, which specifies that bar height should be the `y = ` value, not the number of rows as is the default -* Add the argument `width = ` to avoid vertical white lines between the bars. For daily data set to 1. For weekly count data set to 7. For monthly count data, white lines are an issue (each month has different number of days) - consider transforming your x-axis to a categorical ordered factor (months) and using `geom_col()`. +* Add the argument `width = ` to avoid vertical white lines between the bars For daily data set to 1 For weekly count data set to 7 For monthly count data, white lines are an issue (each month has different number of days) - consider transforming your x-axis to a categorical ordered factor (months) and using `geom_col()` ```{r, message=FALSE, warning=F} -ggplot(data = count_data)+ +ggplot(data = count_data) + geom_histogram( - mapping = aes(x = date_hospitalisation, y = n_cases), + mapping = aes(x = date_hospitalisation, + y = n_cases), stat = "identity", - width = 1)+ # for daily counts, set width = 1 to avoid white space between bars + width = 1) + # for daily counts, set width = 1 to avoid white space between bars labs( x = "Date of report", y = "Number of cases", @@ -818,8 +820,8 @@ If your data are already case counts by week, they might look like this dataset # Create weekly dataset with epiweek column count_data_weekly <- count_data %>% mutate(epiweek = lubridate::floor_date(date_hospitalisation, "week")) %>% - group_by(hospital, epiweek, .drop=F) %>% - summarize(n_cases_weekly = sum(n_cases, na.rm=T)) + group_by(hospital, epiweek, .drop = F) %>% + summarize(n_cases_weekly = sum(n_cases, na.rm = T)) ``` The first 50 rows of `count_data_weekly` are displayed below. You can see that the counts have been aggregated into weeks. Each week is displayed by the first day of the week (Monday by default). @@ -832,7 +834,7 @@ DT::datatable(count_data_weekly, rownames = FALSE, options = list(pageLength = 5 Now plot so that `x = ` the epiweek column. Remember to add `y = ` the counts column to the aesthetic mapping, and add `stat = "identity"` as explained above. ```{r, warning=F, message=F} -ggplot(data = count_data_weekly)+ +ggplot(data = count_data_weekly) + geom_histogram( mapping = aes( @@ -840,18 +842,18 @@ ggplot(data = count_data_weekly)+ y = n_cases_weekly, # y-axis height in the weekly case counts group = hospital, # we are grouping the bars and coloring by hospital fill = hospital), - stat = "identity")+ # this is also required when plotting count data + stat = "identity") + # this is also required when plotting count data # labels for x-axis scale_x_date( date_breaks = "2 months", # labels every 2 months date_minor_breaks = "1 month", # gridlines every month - label = scales::label_date_short())+ # label formatting + label = scales::label_date_short()) + # label formatting # Choose color palette (uses RColorBrewer package) - scale_fill_brewer(palette = "Pastel2")+ + scale_fill_brewer(palette = "Pastel2") + - theme_minimal()+ + theme_minimal() + labs( x = "Week of onset", @@ -867,11 +869,11 @@ ggplot(data = count_data_weekly)+ See the page on [Moving averages](moving_average.qmd) for a detailed description and several options. Below is one option for calculating moving averages with the package **slider**. In this approach, *the moving average is calculated in the dataset prior to plotting*: -1) Aggregate the data into counts as necessary (daily, weekly, etc.) (see [Grouping data](grouping.qmd) page) -2) Create a new column to hold the moving average, created with `slide_index()` from **slider** package -3) Plot the moving average as a `geom_line()` on top of (after) the epicurve histogram +1) Aggregate the data into counts as necessary (daily, weekly, etc.) (see [Grouping data](grouping.qmd) page) +2) Create a new column to hold the moving average, created with `slide_index()` from **slider** package +3) Plot the moving average as a `geom_line()` on top of (after) the epicurve histogram -See the helpful online [vignette for the **slider** package](https://cran.r-project.org/web/packages/slider/vignettes/slider.html) +See the helpful online [vignette for the **slider** package](https://cran.r-project.org/web/packages/slider/vignettes/slider.html). ```{r, warning=F, message=F} @@ -907,7 +909,7 @@ ggplot(data = ll_counts_7day) + # begin with new dataset defined above stat = "identity", # height is y value fill="#92a8d1", # cool color for bars colour = "#92a8d1", # same color for bar border - )+ + ) + geom_line( # make line for rolling average mapping = aes( x = date_onset, # date column for x-axis @@ -925,8 +927,8 @@ ggplot(data = ll_counts_7day) + # begin with new dataset defined above labs( x="", y ="Number of confirmed cases", - fill = "Legend")+ - theme_minimal()+ + fill = "Legend") + + theme_minimal() + theme(legend.title = element_blank()) # removes title of legend ``` @@ -955,9 +957,9 @@ To change the order of appearance, change the underlying order of the levels of **Aesthetics** Font size and face, strip color, etc. can be modified through `theme()` with arguments like: -* `strip.text = element_text()` (size, colour, face, angle...) -* `strip.background = element_rect()` (e.g. element_rect(fill="grey")) -* `strip.position = ` (position of the strip "bottom", "top", "left", or "right") +* `striptext = element_text()` (size, colour, face, angle, etc) +* `stripbackground = element_rect()` (eg element_rect(fill = "grey")) +* `stripposition = ` (position of the strip "bottom", "top", "left", or "right") **Strip labels** @@ -996,33 +998,33 @@ ggplot(central_data) + # histogram breaks breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page) closed = "left" # count cases from start of breakpoint - )+ + ) + # The labels on the x-axis scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "2 months", # labels appear every 2 months date_minor_breaks = "1 month", # vertical lines appear every 1 month - label = scales::label_date_short())+ # label formatting + label = scales::label_date_short()) + # label formatting # y-axis - scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space between bottom of bars and the labels + scale_y_continuous(expand = c(0, 0)) + # removes excess y-axis space between bottom of bars and the labels # aesthetic themes - theme_minimal()+ # a set of themes to simplify plot + theme_minimal() + # a set of themes to simplify plot theme( plot.caption = element_text(face = "italic", hjust = 0), # caption on left side in italics axis.title = element_text(face = "bold"), legend.position = "bottom", strip.text = element_text(face = "bold", size = 10), - strip.background = element_rect(fill = "grey"))+ # axis titles in bold + strip.background = element_rect(fill = "grey")) + # axis titles in bold # create facets facet_wrap( ~age_cat, ncol = 4, strip.position = "top", - labeller = my_labels)+ + labeller = my_labels) + # labels labs( @@ -1059,36 +1061,36 @@ ggplot(central_data) + breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page) closed = "left", # count cases from start of breakpoint - )+ # pre-defined date vector (see top of ggplot section) + ) + # pre-defined date vector (see top of ggplot section) # add grey epidemic in background to each facet - gghighlight::gghighlight()+ + gghighlight::gghighlight() + # labels on x-axis scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "2 months", # labels appear every 2 months date_minor_breaks = "1 month", # vertical lines appear every 1 month - label = scales::label_date_short())+ # label formatting + label = scales::label_date_short()) + # label formatting # y-axis - scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space below 0 + scale_y_continuous(expand = c(0, 0)) + # removes excess y-axis space below 0 # aesthetic themes - theme_minimal()+ # a set of themes to simplify plot + theme_minimal() + # a set of themes to simplify plot theme( plot.caption = element_text(face = "italic", hjust = 0), # caption on left side in italics axis.title = element_text(face = "bold"), legend.position = "bottom", strip.text = element_text(face = "bold", size = 10), - strip.background = element_rect(fill = "white"))+ # axis titles in bold + strip.background = element_rect(fill = "white")) + # axis titles in bold # create facets facet_wrap( ~age_cat, # each plot is one value of age_cat ncol = 4, # number of columns strip.position = "top", # position of the facet title/strip - labeller = my_labels)+ # labeller defines above + labeller = my_labels) + # labeller defines above # labels labs( @@ -1158,28 +1160,28 @@ ggplot(central_data2) + breaks = weekly_breaks_central, # pre-defined date vector (see earlier in this page) closed = "left", # count cases from start of breakpoint - )+ # pre-defined date vector (see top of ggplot section) + ) + # pre-defined date vector (see top of ggplot section) # Labels on x-axis scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "2 months", # labels appear every 2 months date_minor_breaks = "1 month", # vertical lines appear every 1 month - label = scales::label_date_short())+ # automatic label formatting + label = scales::label_date_short()) + # automatic label formatting # y-axis - scale_y_continuous(expand = c(0,0))+ # removes excess y-axis space between bottom of bars and the labels + scale_y_continuous(expand = c(0, 0)) + # removes excess y-axis space between bottom of bars and the labels # aesthetic themes - theme_minimal()+ # a set of themes to simplify plot + theme_minimal() + # a set of themes to simplify plot theme( plot.caption = element_text(face = "italic", hjust = 0), # caption on left side in italics axis.title = element_text(face = "bold"), - legend.position = "bottom")+ + legend.position = "bottom") + # create facets facet_wrap(facet~. , # each plot is one value of facet - ncol = 1)+ + ncol = 1) + # labels labs(title = "Weekly incidence of cases, by age category", @@ -1203,16 +1205,16 @@ ggplot(central_data2) + The most recent data shown in epicurves should often be marked as tentative, or subject to reporting delays. This can be done in by adding a vertical line and/or rectangle over a specified number of days. Here are two options: 1) Use `annotate()`: - + For a line use `annotate(geom = "segment")`. Provide `x`, `xend`, `y`, and `yend`. Adjust size, linetype (`lty`), and color. + + For a line use `annotate(geom = "segment")`. Provide `x`, `xend`, `y`, and `yend`. Adjust size, linetype (`lty`), and color + For a rectangle use `annotate(geom = "rect")`. Provide xmin/xmax/ymin/ymax. Adjust color and alpha. -2) Group the data by tentative status and color those bars differently +2) Group the data by tentative status and color those bars differently **_CAUTION:_** You might try `geom_rect()` to draw a rectangle, but adjusting the transparency does not work in a linelist context. This function overlays one rectangle for each observation/row!. Use either a very low alpha (e.g. 0.01), or another approach. ### Using `annotate()` {.unnumbered} -* Within `annotate(geom = "rect")`, the `xmin` and `xmax` arguments must be given inputs of class Date. -* Note that because these data are aggregated into weekly bars, and the last bar extends to the Monday after the last data point, the shaded region may appear to cover 4 weeks +* Within `annotate(geom = "rect")`, the `xmin` and `xmax` arguments must be given inputs of class Date +* Note that because these data are aggregated into weekly bars, and the last bar extends to the Monday after the last data point, the shaded region may appear to cover 4 weeks * Here is an `annotate()` [online example](https://ggplot2.tidyverse.org/reference/annotate.html) @@ -1232,19 +1234,19 @@ ggplot(central_data) + fill = "lightblue") + # scales - scale_y_continuous(expand = c(0,0))+ + scale_y_continuous(expand = c(0, 0)) + scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "1 month", # 1st of month date_minor_breaks = "1 month", # 1st of month - label = scales::label_date_short())+ # automatic label formatting + label = scales::label_date_short()) + # automatic label formatting # labels and theme labs( title = "Using annotate()\nRectangle and line showing that data from last 21-days are tentative", x = "Week of symptom onset", - y = "Weekly case indicence")+ - theme_minimal()+ + y = "Weekly case indicence") + + theme_minimal() + # add semi-transparent red rectangle to tentative data annotate( @@ -1254,7 +1256,7 @@ ggplot(central_data) + ymin = 0, ymax = Inf, alpha = 0.2, # alpha easy and intuitive to adjust using annotate() - fill = "red")+ + fill = "red") + # add black vertical line on top of other layers annotate( @@ -1265,7 +1267,7 @@ ggplot(central_data) + yend = Inf, # line to top of plot size = 2, # line size color = "black", - lty = "solid")+ # linetype e.g. "solid", "dashed" + lty = "solid") + # linetype e.g. "solid", "dashed" # add text in rectangle annotate( @@ -1310,18 +1312,18 @@ ggplot(plot_data, aes(x = date_onset, fill = tentative)) + color = "black") + # scales - scale_y_continuous(expand = c(0,0))+ - scale_fill_manual(values = c("lightblue", "grey"))+ + scale_y_continuous(expand = c(0, 0)) + + scale_fill_manual(values = c("lightblue", "grey")) + scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars + expand = c(0, 0), # remove excess x-axis space below and after case bars date_breaks = "3 weeks", # Monday every 3 weeks date_minor_breaks = "week", # Monday weeks - label = scales::label_date_short())+ # automatic label formatting + label = scales::label_date_short()) + # automatic label formatting # labels and theme labs(title = "Show days that are tentative reporting", - subtitle = "")+ - theme_minimal()+ + subtitle = "") + + theme_minimal() + theme(legend.title = element_blank()) # remove title of legend ``` @@ -1347,26 +1349,26 @@ ggplot(central_data) + fill = "lightblue") + # y-axis scale as before - scale_y_continuous(expand = c(0,0))+ + scale_y_continuous(expand = c(0, 0)) + # x-axis scale sets efficient date labels scale_x_date( - expand = c(0,0), # remove excess x-axis space below and after case bars - labels = scales::label_date_short())+ # auto efficient date labels + expand = c(0, 0), # remove excess x-axis space below and after case bars + labels = scales::label_date_short()) + # auto efficient date labels # labels and theme labs( title = "Using label_date_short()\nTo make automatic and efficient date labels", x = "Week of symptom onset", - y = "Weekly case indicence")+ + y = "Weekly case indicence") + theme_minimal() ``` A second option is to use faceting. Below: -* Case counts are aggregated into weeks for aesthetic reasons. See Epicurves page (aggregated data tab) for details. -* A `geom_area()` line is used instead of a histogram, as the faceting approach below does not work well with histograms. +* Case counts are aggregated into weeks for aesthetic reasons. See Epicurves page (aggregated data tab) for details +* A `geom_area()` line is used instead of a histogram, as the faceting approach below does not work well with histograms **Aggregate to weekly counts** @@ -1399,12 +1401,12 @@ ggplot(central_weekly, color = "#69b3a2") + # line color geom_point(size=1, color="#69b3a2") + # make points at the weekly data points geom_area(fill = "#69b3a2", # fill area below line - alpha = 0.4)+ # fill transparency + alpha = 0.4) + # fill transparency scale_x_date(date_labels="%b", # date label format show month date_breaks="month", # date labels on 1st of each month - expand=c(0,0)) + # remove excess space + expand=c(0, 0)) + # remove excess space scale_y_continuous( - expand = c(0,0))+ # remove excess space below x-axis + expand = c(0, 0)) + # remove excess space below x-axis facet_grid(~lubridate::year(week), # facet on year (of Date class column) space="free_x", scales="free_x", # x-axes adapt to data range (not "fixed") @@ -1414,7 +1416,7 @@ ggplot(central_weekly, strip.background = element_blank(), # no facet lable background panel.grid.minor.x = element_blank(), panel.border = element_blank(), # no border for facet panel - panel.spacing=unit(0,"cm"))+ # No space between facet panels + panel.spacing=unit(0,"cm")) + # No space between facet panels labs(title = "Nested year labels - points, shaded, no label border") ``` @@ -1504,7 +1506,6 @@ plot_deaths <- linelist %>% # begin with linelist ``` Now use **cowplot** to overlay the two plots. Attention has been paid to the x-axis alignment, side of the y-axis, and use of `theme_cowplot()`. - ```{r, warning=F, message=F} aligned_plots <- cowplot::align_plots(plot_cases, plot_deaths, align="hv", axis="tblr") ggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]]) @@ -1512,7 +1513,6 @@ ggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]]) - ## Cumulative Incidence {} If beginning with a case linelist, create a new column containing the cumulative number of cases per day in an outbreak using `cumsum()` from **base** R: @@ -1537,7 +1537,7 @@ DT::datatable(head(cumulative_case_counts, 10), rownames = FALSE, options = list This cumulative column can then be plotted against `date_onset`, using `geom_line()`: ```{r, warning=F, message=F} -plot_cumulative <- ggplot()+ +plot_cumulative <- ggplot() + geom_line( data = cumulative_case_counts, aes(x = date_onset, y = cumulative_cases), @@ -1548,55 +1548,221 @@ plot_cumulative ``` -It can also be overlaid onto the epicurve, with dual-axis using the **cowplot** method described above and in the [ggplot tips](ggplot_tips.qmd) page: +It can also be overlaid onto the epicurve, with dual-axis using the method described above and in the [ggplot tips](ggplot_tips.qmd) page. + +## incidence2 + +Below we demonstrate how to make epicurves using the **incidence2** package. The authors of this package have tried to allow the user to create and modify epicurves without needing to know **ggplot2** syntax. Much of this page is adapted from the package vignettes, which can be found at the **incidence2** [github page](https://www.reconverse.org/incidence2/articles/incidence2.html). + +While **incidence2** can be very useful for quickly generating figures, the package is much less flexible than approaches described using **ggplot2**. While this may not be an issue for some users, those who want to have a greater control in creating and adapting their figures, should use **ggplot2**. + +To create an epicurve with **incidence2** you need to have a column with a date value (it does not need to be of the class `Date`, but it should have a numeric or logical order to it (i.e. "Week1", "Week2", etc)) and a column with a count variable (what is being counted). It should also *not* have any duplicated rows. + +To create this, we can use the function `incidence()` which will summarise our data in a format that can be used to create epicurves. There are a number of different arguments to `incidence()`, type `?incidence` in your R console to learn more. + +```{r} +#Load package +pacman::p_load( + incidence2 +) + +#Create the incidence object +epi_day <- linelist %>% + incidence(date_index = "date_onset", + interval = "day") + +epi_day +``` + +The object created by the function `incidence()` looks like a data frame, but has its own class of `incidence2`. + +```{r} +class(epi_day) +``` + +To plot the epicurve, you use the function `plot()`. To read the **incidence2** specific documentation on plotting, type `?plot.incidence2`. + +```{r} +plot(epi_day) +``` +If you notice lots of tiny white vertical lines, try to adjust the size of your image. For example, if you export your plot with `ggsave()`, you can provide numbers to `width = ` and `height = `. If you widen the plot those lines may disappear. + + +### Change time interval of case aggregation {.unnumbered} +The `interval = ` argument of `incidence()` defines how the observations are grouped into vertical bars. + +**Specify interval** + +**incidence2** provides flexibility and understandable syntax for specifying how you want to aggregate your cases into epicurve bars. Provide a value like the ones below to the `interval = ` argument. Below are examples of how different intervals look when applied to the linelist. Note how the default format and frequency of the date *labels* on the x-axis change as the date interval changes. + +```{r incidence, out.width=c('50%', '50%', '50%', '50%'), fig.show='hold', warning=F, message=F} + +# Create the incidence objects (with different intervals) +epi_epiweek <- incidence(linelist, "date_onset", interval = "epiweek") # epiweek +epi_month <- incidence(linelist, "date_onset", interval = "month") # Monthly +epi_quarter <- incidence(linelist, "date_onset", interval = "quarter") # Quarterly +epi_year <- incidence(linelist, "date_onset", interval = "year") # Years + +# Plot the incidence objects (+ titles for clarity) +############################ +plot(epi_epiweek) + labs(title = "2 (Monday) weeks") +plot(epi_month) + labs(title = "Months") +plot(epi_quarter) + labs(title = "Quarters") +plot(epi_year) + labs(title = "Years") +``` +**First date** + +You can optionally specify a value of class Date (e.g. `as.Date("2016-05-01")`) to `firstdate = ` in the `incidence()` command. If given, the data will be trimmed to this range and the intervals will begin on this date. + +### Groups {.unnumbered} + +Groups are specified in the `incidence()` command, and can be used to color the bars or to facet the data. To specify groups in your data provide the column name(s) to the `groups =` argument in the `incidence()` command (no quotes around the column name). If specifying multiple columns, put their names within `c()`. + +You can specify that cases with missing values in the grouping columns be listed as a distinct `NA` group by setting `na_as_group = TRUE`. Otherwise, they will be excluded from the plot. +* To *color the bars by a grouping column*, you must again provide the column name to `fill = ` in the `plot()` command +* To *facet based on a grouping column*, see the section below on facets with **incidence2** + +In the example below, the cases in the whole outbreak are grouped by their age category. Missing values are included as a group. The epicurve interval is weeks. + +```{r, message=F, warning=F} + +# Create incidence object, with data grouped by age category +age_outbreak <- incidence( + linelist, # dataset + date_index = "date_onset", # date column + interval = "week", # Monday weekly aggregation of cases + groups = "age_cat" # age_cat is set as a group +) + +# plot the grouped incidence object +plot( + age_outbreak, # incidence object with age_cat as group + fill = "age_cat") + # age_cat is used for bar fill color (must have been set as a groups column above) +labs(fill = "Age Category") # change legend title from default "age_cat" (this is a ggplot2 modification) +``` +**_TIP:_** Change the title of the legend by adding `+` the **ggplot2** command `labs(fill = "your title")` to your **incidence2** plot. + +You can also have the grouped bars display side-by-side by setting `stack = FALSE` in `plot()`, as shown below: ```{r, warning=F, message=F} -#load package -pacman::p_load(cowplot) -# Make first plot of epicurve histogram -plot_cases <- ggplot()+ - geom_histogram( - data = linelist, - aes(x = date_onset), - binwidth = 1)+ - labs( - y = "Daily cases", - x = "Date of symptom onset" - )+ - theme_cowplot() +# Make incidence object of monthly counts. +monthly_gender <- incidence( + linelist, + date_index = "date_onset", + interval = "month", + groups = "gender" # set gender as grouping column +) -# make second plot of cumulative cases line -plot_cumulative <- ggplot()+ - geom_line( - data = cumulative_case_counts, - aes(x = date_onset, y = cumulative_cases), - size = 2, - color = "blue")+ - scale_y_continuous( - position = "right")+ - labs(x = "", - y = "Cumulative cases")+ - theme_cowplot()+ - theme( - axis.line.x = element_blank(), - axis.text.x = element_blank(), - axis.title.x = element_blank(), - axis.ticks = element_blank()) +plot( + monthly_gender, # incidence object + fill = "gender", # display bars colored by gender + stack = FALSE) # side-by-side (not stacked) +``` +You can set the `na_as_group = ` argument to FALSE in the `incidence()` command to remove rows with missing values from the plot. + +### Filtered data {.unnumbered} + +To plot the epicurve of a subset of data: + +1) Filter the linelist data +2) Provide the filtered data to the `incidence()` command +3) Plot the incidence object + +The example below uses data filtered to show only cases at Central Hospital. + +```{r, warning=F, message=F} +# filter the linelist +central_data <- linelist %>% + filter(hospital == "Central Hospital") + +# create incidence object using filtered data +central_outbreak <- incidence(central_data, date_index = "date_onset", interval = "week") + +# plot the incidence object +plot(central_outbreak, title = "Weekly case incidence at Central Hospital") ``` +### Aggregated counts {.unnumbered} -Now use **cowplot** to overlay the two plots. Attention has been paid to the x-axis alignment, side of the y-axis, and use of `theme_cowplot()`. +If your original data are aggregated (counts), provide the name of the column that contains the case counts to the `count = ` argument when creating the incidence object with `incidence()`. + +For example, this data frame `count_data` is the linelist aggregated into daily counts by hospital. The first 50 rows look like this: + +```{r message=FALSE, echo=F} +DT::datatable(head(count_data, 50), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) +``` + +If you are beginning your analysis with daily count data like the dataset above, your `incidence()` command to convert this to a weekly epicurve by hospital would look like this: + +```{r} + +epi_counts <- incidence( # create weekly incidence object + count_data, # dataset with counts aggregated by day + date_index = "date_hospitalisation", # column with dates + counts = "n_cases", # column with counts + interval = "week", # aggregate daily counts up to weeks + groups = "hospital" # group by hospital + ) + +# plot the weekly incidence epi curve, with stacked bars by hospital +plot(epi_counts, # incidence object + fill = "hospital") # color the bars by hospital +``` +### Facets/small multiples {.unnumbered} + +Below, we set both columns `hospital` and `outcome` as grouping columns in the `incidence()` command. For grouped data, the plot method will create a faceted plot across groups unless a fill variable is specified. Note we are dropping `NA` values only for visualisation purposes. + ```{r, warning=F, message=F} -aligned_plots <- cowplot::align_plots(plot_cases, plot_cumulative, align="hv", axis="tblr") -ggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]]) +epi_wks_hosp_out <- incidence( + linelist %>% + select(date_onset, + outcome, + gender) %>% + drop_na(), # dataset + date_index = "date_onset", # date column + interval = "quarter", # monthly bars + groups = c("outcome", "gender") # both outcome and hospital are given as grouping columns + ) + +# plot +plot(epi_wks_hosp_out) + ``` +Note that the package **ggtree** (used for displaying phylogenetic trees) also has a function `facet_plot()`. +### Modifications with `plot()` and using ggplot2 {.unnumbered} - -## Resources { } +To see further examples of plot modification, please see the **incidence2** vignette on [customizing incidence plots](https://ftp.eenet.ee/pub/cran/web/packages/incidence2/vignettes/customizing_incidence_plots.html). + +Additionally, you can use **ggplot2()** commands after calling `plot(incidence)` to fully customise your plots. + +For example here we are going to change the theme, axis labels, and add a trend line (as specified in the chapter on [Moving averages](moving_average.qmd)) of the first plot we created with the object `epi_day`. + +```{r} +plot(epi_day) + + # plot moving average + tidyquant::geom_ma( + mapping = aes(x = date_index, + y = count), + n = 7, + size = 1, + color = "red", + linetype = "solid") + + labs(x = "Onset date", + y = "Daily cases", + title = "Epicurve of Ebola cases", + fill = "") + + theme_dark() + + theme(legend.position = "none") + +``` + + +## Resources { } +[incidence2](https://www.reconverse.org/incidence2/index.html) diff --git a/new_pages/epidemic_models.qmd b/new_pages/epidemic_models.qmd index d30a9cf7..a82a02a9 100644 --- a/new_pages/epidemic_models.qmd +++ b/new_pages/epidemic_models.qmd @@ -9,9 +9,9 @@ There exists a growing body of tools for epidemic modelling that lets us conduct fairly complex analyses with minimal effort. This section will provide an overview on how to use these tools to: -* estimate the effective reproduction number Rt and related statistics - such as the doubling time -* produce short-term projections of future incidence +* Estimate the effective reproduction number Rt and related statistics + such as the doubling time. +* Produce short-term projections of future incidence. It is *not* intended as an overview of the methodologies and statistical methods underlying these tools, so please refer to the Resources tab for links to some @@ -139,7 +139,7 @@ pacman::p_load( We will use the cleaned case linelist for all analyses in this section. If you want to follow along, click to download the "clean" linelist (as .rds file). See the [Download handbook and data](data_used.qmd) page to download all example data used in this handbook. -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -302,7 +302,8 @@ that **EpiNow2** requires the column names to be `date` and `confirm`. ## get incidence from onset dates cases <- linelist %>% group_by(date = date_onset) %>% - summarise(confirm = n()) + summarise(confirm = n()) %>% + drop_na() #epinow wont run with NA values ``` We can then estimate Rt using the `epinow` function. Some notes on @@ -324,7 +325,7 @@ the inputs: ## run epinow epinow_res <- epinow( reported_cases = cases, - generation_time = generation_time, + generation_time = generation_time_opts(generation_time), delays = delay_opts(incubation_period), return_output = TRUE, verbose = TRUE, @@ -472,7 +473,8 @@ cases <- incidence2::incidence(linelist, date_index = "date_onset") %>% # get ca by = "day"), fill = list(count = 0)) %>% # convert NA counts to 0 rename(I = count, # rename to names expected by estimateR - dates = date_index) + dates = date_index + ) ``` The package provides several options for specifying the serial interval, the @@ -984,7 +986,7 @@ DT::datatable( * [Here is the paper](https://www.sciencedirect.com/science/article/pii/S1755436519300350) describing the methodology implemented in **EpiEstim**. -* [Here is the paper](https://wellcomeopenresearch.org/articles/5-112/v1) describing +* [Here is the paper](https://wellcomeopenresearch.org/articles/5-112) describing the methodology implemented in **EpiNow2**. * [Here is a paper](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1008409) describing various methodological and practical considerations for estimating Rt. diff --git a/new_pages/errors.qmd b/new_pages/errors.qmd index 49b2f8fd..add9a816 100644 --- a/new_pages/errors.qmd +++ b/new_pages/errors.qmd @@ -6,14 +6,14 @@ This page includes a running list of common errors and suggests solutions for tr ## Interpreting error messages -R errors can be cryptic at times, so Google is your friend. Search the error message with "R" and look for recent posts in [StackExchange.com](StackExchange.com), [stackoverflow.com](stackoverflow.com), [community.rstudio.com](community.rstudio.com), twitter (#rstats), and other forums used by programmers to filed questions and answers. Try to find recent posts that have solved similar problems. +R errors can be cryptic at times, so Google is your friend. Search the error message with "R" and look for recent posts in [StackExchange.com](www.StackExchange.com), [stackoverflow.com](https://stackoverflow.com/), [forum.posit.co](forum.posit.co), twitter (#rstats), and other forums used by programmers to filed questions and answers. Try to find recent posts that have solved similar problems. If after much searching you cannot find an answer to your problem, consider creating a *reproducible example* ("reprex") and posting the question yourself. See the page on [Getting help](help.qmd) for tips on how to create and post a reproducible example to forums. ## Common errors -Below, we list some common errors and potential explanations/solutions. Some of these are borrowed from Noam Ross who analyzed the most common forum posts on Stack Overflow about R error messages (see analysis [here](https://github.com/noamross/zero-dependency-problems/blob/master/misc/stack-overflow-common-r-errors.md)) +Below, we list some common errors and potential explanations/solutions. Some of these are borrowed from Noam Ross who analyzed the most common forum posts on Stack Overflow about R error messages (see analysis [here](https://github.com/noamross/zero-dependency-problems/blob/master/misc/stack-overflow-common-r-errors.md)). ### Typo errors {.unnumbered} @@ -23,7 +23,7 @@ Error: unexpected symbol in: " geom_histogram(stat = "identity")+ tidyquant::geom_ma(n=7, size = 2, color = "red" lty" ``` -If you see "unexpected symbol", check for missing commas +If you see "unexpected symbol", check for missing commas. @@ -95,7 +95,7 @@ This error above (`argument .x is missing, with no default`) is common in `mutat Error in if ``` -This likely means an `if` statement was applied to something that was not TRUE or FALSE. +This likely means an `if` statement was applied to something that was not `TRUE` or `FALSE.` ### Factor errors {.unnumbered} @@ -112,14 +112,14 @@ If you see this error about invalid factor levels, you likely have a column of c ### Plotting errors {.unnumbered} `Error: Insufficient values in manual scale. 3 needed but only 2 provided.` -ggplot() scale_fill_manual() values = c("orange", "purple") ... insufficient for number of factor levels ... consider whether NA is now a factor level... + +You may have supplied too few values for the color scale, make sure you have the same number of colors as values in your plot. Consider checking whether `NA` is a factor level in your dataset. ``` Can't add x object ``` You probably have an extra `+` at the end of a ggplot command that you need to delete. - ### R Markdown errors {.unnumbered} If the error message contains something like `Error in options[[sprintf("fig.%s", i)]]`, check that your knitr options at the top of each chunk correctly use the `out.width = ` or `out.height = ` and *not* `fig.width=` and `fig.height=`. @@ -135,3 +135,8 @@ Consider whether you re-arranged piped **dplyr** verbs and didn't replace a pipe ## Resources { } This is another blog post that lists common [R programming errors faced by beginners](https://www.r-bloggers.com/2016/06/common-r-programming-errors-faced-by-beginners/) + +[Debugging your code](https://rstats.wtf/debugging-r) + +[Advanced debugging](https://adv-r.hadley.nz/debugging.html) + diff --git a/new_pages/factors.html b/new_pages/factors.html new file mode 100644 index 00000000..f456e7cf --- /dev/null +++ b/new_pages/factors.html @@ -0,0 +1,1962 @@ + + + + + + + + + +The Epidemiologist R Handbook - 11  Factors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    11  Factors

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    In R, factors are a class of data that allow for ordered categories with a fixed set of acceptable values.

    +

    Typically, you would convert a column from character or numeric class to a factor if you want to set an intrinsic order to the values (“levels”) so they can be displayed non-alphabetically in plots and tables. Another common use of factors is to standardise the legends of plots so they do not fluctuate if certain values are temporarily absent from the data.

    +

    This page demonstrates use of functions from the package forcats (a short name for “For categorical variables”) and some base R functions. We also touch upon the use of lubridate and aweek for special factor cases related to epidemiological weeks.

    +

    A complete list of forcats functions can be found online here. Below we demonstrate some of the most common ones.

    + +
    +

    11.1 Preparation

    +
    +

    Load packages

    +

    This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.

    +
    +
    pacman::p_load(
    +  rio,           # import/export
    +  here,          # filepaths
    +  lubridate,     # working with dates
    +  forcats,       # factors
    +  aweek,         # create epiweeks with automatic factor levels
    +  janitor,       # tables
    +  tidyverse      # data mgmt and viz
    +  )
    +
    +
    +
    +

    Import data

    +

    We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). Import your data with the import() function from the rio package (it accepts many file types like .xlsx, .rds, .csv - see the Import and export page for details).

    +
    +
    +
    Warning: The `trust` argument of `import()` should be explicit for serialization formats
    +as of rio 1.0.3.
    +ℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.
    +ℹ The deprecated feature was likely used in the rio package.
    +  Please report the issue at <https://github.com/gesistsa/rio/issues>.
    +
    +
    +
    +
    # import your dataset
    +linelist <- import("linelist_cleaned.rds")
    +
    +
    +
    +

    New categorical variable

    +

    For demonstration in this page we will use a common scenario - the creation of a new categorical variable.

    +

    Note that if you convert a numeric column to class factor, you will not be able to calculate numeric statistics on it.

    +
    +

    Create column

    +

    We use the existing column days_onset_hosp (days from symptom onset to hospital admission) and create a new column delay_cat by classifying each row into one of several categories. We do this with the dplyr function case_when(), which sequentially applies logical criteria (right-side) to each row and returns the corresponding left-side value for the new column delay_cat. Read more about case_when() in Cleaning data and core functions.

    +
    +
    linelist <- linelist %>% 
    +  mutate(delay_cat = case_when(
    +    # criteria                                   # new value if TRUE
    +    days_onset_hosp < 2                        ~ "<2 days",
    +    days_onset_hosp >= 2 & days_onset_hosp < 5 ~ "2-5 days",
    +    days_onset_hosp >= 5                       ~ ">5 days",
    +    is.na(days_onset_hosp)                     ~ NA_character_,
    +    TRUE                                       ~ "Check me"))  
    +
    +
    +
    +

    Default value order

    +

    As created with case_when(), the new column delay_cat is a categorical column of class Character - not yet a factor. Thus, in a frequency table, we see that the unique values appear in a default alpha-numeric order - an order that does not make much intuitive sense:

    +
    +
    table(linelist$delay_cat, useNA = "always")
    +
    +
    
    + <2 days  >5 days 2-5 days     <NA> 
    +    2990      602     2040      256 
    +
    +
    +

    Likewise, if we make a bar plot, the values also appear in this order on the x-axis (see the ggplot basics page for more on ggplot2 - the most common visualization package in R).

    +
    +
    ggplot(data = linelist) +
    +  geom_bar(mapping = aes(x = delay_cat))
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +
    +
    +

    11.2 Convert to factor

    +

    To convert a character or numeric column to class factor, you can use any function from the forcats package (many are detailed below). They will convert to class factor and then also perform or allow certain ordering of the levels - for example using fct_relevel() lets you manually specify the level order. The function as_factor() simply converts the class without any further capabilities.

    +

    The base R function factor() converts a column to factor and allows you to manually specify the order of the levels, as a character vector to its levels = argument.

    +

    Below we use mutate() and fct_relevel() to convert the column delay_cat from class character to class factor. The column delay_cat is created in the Preparation section above.

    +
    +
    linelist <- linelist %>%
    +  mutate(delay_cat = fct_relevel(delay_cat))
    +
    +

    The unique “values” in this column are now considered “levels” of the factor. The levels have an order, which can be printed with the base R function levels(), or alternatively viewed in a count table via table() from base R or tabyl() from janitor. By default, the order of the levels will be alpha-numeric, as before. Note that NA is not a factor level.

    +
    +
    levels(linelist$delay_cat)
    +
    +
    [1] "<2 days"  ">5 days"  "2-5 days"
    +
    +
    +

    The function fct_relevel() has the additional utility of allowing you to manually specify the level order. Simply write the level values in order, in quotation marks, separated by commas, as shown below. Note that the spelling must exactly match the values. If you want to create levels that do not exist in the data, use fct_expand() instead).

    +
    +
    linelist <- linelist %>%
    +  mutate(delay_cat = fct_relevel(delay_cat, "<2 days", "2-5 days", ">5 days"))
    +
    +

    We can now see that the levels are ordered, as specified in the previous command, in a sensible order.

    +
    +
    levels(linelist$delay_cat)
    +
    +
    [1] "<2 days"  "2-5 days" ">5 days" 
    +
    +
    +

    Now the plot order makes more intuitive sense as well.

    +
    +
    ggplot(data = linelist) +
    +  geom_bar(mapping = aes(x = delay_cat))
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    11.3 Add or drop levels

    +
    +

    Add

    +

    If you need to add levels to a factor, you can do this with fct_expand(). Just write the column name followed by the new levels (separated by commas). By tabulating the values, we can see the new levels and the zero counts. You can use table() from base R, or tabyl() from janitor:

    +
    +
    linelist %>% 
    +  mutate(delay_cat = fct_expand(delay_cat, "Not admitted to hospital", "Transfer to other jurisdiction")) %>% 
    +  tabyl(delay_cat)   # print table
    +
    +
                          delay_cat    n    percent valid_percent
    +                        <2 days 2990 0.50781250     0.5308949
    +                       2-5 days 2040 0.34646739     0.3622159
    +                        >5 days  602 0.10224185     0.1068892
    +       Not admitted to hospital    0 0.00000000     0.0000000
    + Transfer to other jurisdiction    0 0.00000000     0.0000000
    +                           <NA>  256 0.04347826            NA
    +
    +
    +

    Note: there is a special forcats function to easily add missing values (NA) as a level. See the section on Missing values below.

    +
    +
    +

    Drop

    +

    If you use fct_drop(), the “unused” levels with zero counts will be dropped from the set of levels. The levels we added above (“Not admitted to a hospital”) exists as a level but no rows actually have those values. So they will be dropped by applying fct_drop() to our factor column:

    +
    +
    linelist %>% 
    +  mutate(delay_cat = fct_drop(delay_cat)) %>% 
    +  tabyl(delay_cat)
    +
    +
     delay_cat    n    percent valid_percent
    +   <2 days 2990 0.50781250     0.5308949
    +  2-5 days 2040 0.34646739     0.3622159
    +   >5 days  602 0.10224185     0.1068892
    +      <NA>  256 0.04347826            NA
    +
    +
    +
    +
    +
    +

    11.4 Adjust level order

    +

    The package forcats offers useful functions to easily adjust the order of a factor’s levels (after a column been defined as class factor):

    +

    These functions can be applied to a factor column in two contexts:

    +
      +
    1. To the column in the data frame, as usual, so the transformation is available for any subsequent use of the data.
      +
    2. +
    3. Inside of a plot, so that the change is applied only within the plot.
    4. +
    +
    +

    Manually

    +

    This function is used to manually order the factor levels. If used on a non-factor column, the column will first be converted to class factor.

    +

    Within the parentheses first provide the factor column name, then provide either:

    +
      +
    • All the levels in the desired order (as a character vector c()), or,
      +
    • +
    • One level and it’s corrected placement using the after = argument.
    • +
    +

    Here is an example of redefining the column delay_cat (which is already class Factor) and specifying all the desired order of levels.

    +
    +
    # re-define level order
    +linelist <- linelist %>% 
    +  mutate(delay_cat = fct_relevel(delay_cat, c("<2 days", "2-5 days", ">5 days")))
    +
    +

    If you only want to move one level, you can specify it to fct_relevel() alone and give a number to the after = argument to indicate where in the order it should be. For example, the command below shifts “<2 days” to the second position:

    +
    +
    # re-define level order
    +linelist %>% 
    +  mutate(delay_cat = fct_relevel(delay_cat, "<2 days", after = 1)) %>% 
    +  tabyl(delay_cat)
    +
    +
    +
    +

    Within a plot

    +

    The forcats commands can be used to set the level order in the data frame, or only within a plot. By using the command to “wrap around” the column name within the ggplot() plotting command, you can reverse/relevel/etc. the transformation will only apply within that plot.

    +

    Below, two plots are created with ggplot() (see the ggplot basics page). In the first, the delay_cat column is mapped to the x-axis of the plot, with it’s default level order as in the data linelist. In the second example it is wrapped within fct_relevel() and the order is changed in the plot.

    +
    +
    # Alpha-numeric default order - no adjustment within ggplot
    +ggplot(data = linelist) +
    +    geom_bar(mapping = aes(x = delay_cat))
    +
    +# Factor level order adjusted within ggplot
    +ggplot(data = linelist) +
    +  geom_bar(mapping = aes(x = fct_relevel(delay_cat, c("<2 days", "2-5 days", ">5 days"))))
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Note that default x-axis title is now quite complicated - you can overwrite this title with the ggplot2 labs() argument.

    +
    +
    +

    Reverse

    +

    It is rather common that you want to reverse the level order. Simply wrap the factor with fct_rev().

    +

    Note that if you want to reverse only a plot legend but not the actual factor levels, you can do that with guides() (see ggplot tips).

    +
    +
    +

    By frequency

    +

    To order by frequency that the value appears in the data, use fct_infreq(). Any missing values (NA) will automatically be included at the end, unless they are converted to an explicit level (see this section). You can reverse the order by further wrapping with fct_rev().

    +

    This function can be used within a ggplot(), as shown below.

    +
    +
    # ordered by frequency
    +ggplot(data = linelist, aes(x = fct_infreq(delay_cat))) +
    +  geom_bar() +
    +  labs(x = "Delay onset to admission (days)",
    +       title = "Ordered by frequency")
    +
    +# reversed frequency
    +ggplot(data = linelist, aes(x = fct_rev(fct_infreq(delay_cat)))) +
    +  geom_bar() +
    +  labs(x = "Delay onset to admission (days)",
    +       title = "Reverse of order by frequency")
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    By appearance

    +

    Use fct_inorder() to set the level order to match the order of appearance in the data, starting from the first row. This can be useful if you first carefully arrange() the data in the data frame, and then use this to set the factor order.

    +
    +
    +

    By summary statistic of another column

    +

    You can use fct_reorder() to order the levels of one column by a summary statistic of another column. Visually, this can result in pleasing plots where the bars/points ascend or descend steadily across the plot.

    +

    In the examples below, the x-axis is delay_cat, and the y-axis is numeric column ct_blood (cycle-threshold value). Box plots show the CT value distribution by delay_cat group. We want to order the box plots in ascending order by the group median CT value.

    +

    In the first example below, the default order alpha-numeric level order is used. You can see the box plot heights are jumbled and not in any particular order. In the second example, the delay_cat column (mapped to the x-axis) has been wrapped in fct_reorder(), the column ct_blood is given as the second argument, and “median” is given as the third argument (you could also use “max”, “mean”, “min”, etc). Thus, the order of the levels of delay_cat will now reflect ascending median CT values of each delay_cat group’s median CT value. This is reflected in the second plot - the box plots have been re-arranged to ascend. Note how NA (missing) will appear at the end, unless converted to an explicit level.

    +
    +
    # boxplots ordered by original factor levels
    +ggplot(data = linelist) +
    +  geom_boxplot(
    +    aes(x = delay_cat,
    +        y = ct_blood, 
    +        fill = delay_cat)) +
    +  labs(x = "Delay onset to admission (days)",
    +       title = "Ordered by original alpha-numeric levels") +
    +  theme_classic() +
    +  theme(legend.position = "none")
    +
    +
    +# boxplots ordered by median CT value
    +ggplot(data = linelist) +
    +  geom_boxplot(
    +    aes(x = fct_reorder(delay_cat, ct_blood, "median"),
    +        y = ct_blood,
    +        fill = delay_cat)) +
    +  labs(x = "Delay onset to admission (days)",
    +       title = "Ordered by median CT value in group") +
    +  theme_classic() +
    +  theme(legend.position = "none")
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    Note in this example above there are no steps required prior to the ggplot() call - the grouping and calculations are all done internally to the ggplot command.

    +
    +
    +

    By “end” value

    +

    Use fct_reorder2() for grouped line plots. It orders the levels (and therefore the legend) to align with the vertical ordering of the lines at the “end” of the plot. Technically speaking, it “orders by the y-values associated with the largest x values.”

    +

    For example, if you have lines showing case counts by hospital over time, you can apply fct_reorder2() to the color = argument within aes(), such that the vertical order of hospitals appearing in the legend aligns with the order of lines at the terminal end of the plot. Read more in the online documentation.

    +
    +
    epidemic_data <- linelist %>%         # begin with the linelist   
    +    filter(date_onset < as.Date("2014-09-21")) %>%    # cut-off date, for visual clarity
    +    count(                                            # get case counts per week and by hospital
    +      epiweek = lubridate::floor_date(date_onset, "week"),  
    +      hospital                                            
    +    ) 
    +  
    +ggplot(data = epidemic_data) +                       # start plot
    +  geom_line(                                        # make lines
    +    aes(
    +      x = epiweek,                                  # x-axis epiweek
    +      y = n,                                        # height is number of cases per week
    +      color = fct_reorder2(hospital, epiweek, n))) + # data grouped and colored by hospital, with factor order by height at end of plot
    +  labs(title = "Factor levels (and legend display) by line height at end of plot",
    +       color = "Hospital")                          # change legend title
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +
    +

    11.5 Missing values

    +

    If you have NA values in your factor column, you can easily convert them to a named level such as “Missing” with fct_explicit_na(). The NA values are converted to “(Missing)” at the end of the level order by default. You can adjust the level name with the argument na_level =.

    +

    Below, this opertation is performed on the column delay_cat and a table is printed with tabyl() with NA converted to “Missing delay”.

    +
    +
    linelist %>% 
    +  mutate(delay_cat = fct_explicit_na(delay_cat, na_level = "Missing delay")) %>% 
    +  tabyl(delay_cat)
    +
    +
    Warning: There was 1 warning in `mutate()`.
    +ℹ In argument: `delay_cat = fct_explicit_na(delay_cat, na_level = "Missing
    +  delay")`.
    +Caused by warning:
    +! `fct_explicit_na()` was deprecated in forcats 1.0.0.
    +ℹ Please use `fct_na_value_to_level()` instead.
    +
    +
    +
         delay_cat    n    percent
    +      2-5 days 2040 0.34646739
    +       <2 days 2990 0.50781250
    +       >5 days  602 0.10224185
    + Missing delay  256 0.04347826
    +
    +
    +
    +
    +

    11.6 Combine levels

    +
    +

    Manually

    +

    You can adjust the level displays manually manually with fct_recode(). This is like the dplyr function recode() (see Cleaning data and core functions), but it allows the creation of new factor levels. If you use the simple recode() on a factor, new re-coded values will be rejected unless they have already been set as permissible levels.

    +

    This tool can also be used to “combine” levels, by assigning multiple levels the same re-coded value. Just be careful to not lose information! Consider doing these combining steps in a new column (not over-writing the existing column).

    +

    DANGER: fct_recode() has a different syntax than recode(). recode() uses OLD = NEW, whereas fct_recode() uses NEW = OLD.

    +

    The current levels of delay_cat are:

    +
    +
    levels(linelist$delay_cat)
    +
    +
    [1] "<2 days"  "2-5 days" ">5 days" 
    +
    +
    +

    The new levels are created using syntax fct_recode(column, "new" = "old", "new" = "old", "new" = "old") and printed:

    +
    +
    linelist %>% 
    +  mutate(delay_cat = fct_recode(
    +    delay_cat,
    +    "Less than 2 days" = "<2 days",
    +    "2 to 5 days"      = "2-5 days",
    +    "More than 5 days" = ">5 days")) %>% 
    +  tabyl(delay_cat)
    +
    +
            delay_cat    n    percent valid_percent
    + Less than 2 days 2990 0.50781250     0.5308949
    +      2 to 5 days 2040 0.34646739     0.3622159
    + More than 5 days  602 0.10224185     0.1068892
    +             <NA>  256 0.04347826            NA
    +
    +
    +

    Here they are manually combined with fct_recode(). Note there is no error raised at the creation of a new level “Less than 5 days”.

    +
    +
    linelist %>% 
    +  mutate(delay_cat = fct_recode(
    +    delay_cat,
    +    "Less than 5 days" = "<2 days",
    +    "Less than 5 days" = "2-5 days",
    +    "More than 5 days" = ">5 days")) %>% 
    +  tabyl(delay_cat)
    +
    +
            delay_cat    n    percent valid_percent
    + Less than 5 days 5030 0.85427989     0.8931108
    + More than 5 days  602 0.10224185     0.1068892
    +             <NA>  256 0.04347826            NA
    +
    +
    +
    +
    +

    Reduce into “Other”

    +

    You can use fct_other() to manually assign factor levels to an “Other” level. Below, all levels in the column hospital, aside from “Port Hospital” and “Central Hospital”, are combined into “Other”. You can provide a vector to either keep =, or drop =. You can change the display of the “Other” level with other_level =.

    +
    +
    linelist %>%    
    +  mutate(hospital = fct_other(                      # adjust levels
    +    hospital,
    +    keep = c("Port Hospital", "Central Hospital"),  # keep these separate
    +    other_level = "Other Hospital")) %>%            # All others as "Other Hospital"
    +  tabyl(hospital)                                   # print table
    +
    +
             hospital    n    percent
    + Central Hospital  454 0.07710598
    +    Port Hospital 1762 0.29925272
    +   Other Hospital 3672 0.62364130
    +
    +
    +
    +
    +

    Reduce by frequency

    +

    You can combine the least-frequent factor levels automatically using fct_lump().

    +

    To “lump” together many low-frequency levels into an “Other” group, do one of the following:

    +
      +
    • Set n = as the number of groups you want to keep. The n most-frequent levels will be kept, and all others will combine into “Other”.
      +
    • +
    • Set prop = as the threshold frequency proportion for levels above which you want to keep. All other values will combine into “Other”.
    • +
    +

    You can change the display of the “Other” level with other_level =. Below, all but the two most-frequent hospitals are combined into “Other Hospital”.

    +
    +
    linelist %>%    
    +  mutate(hospital = fct_lump(                      # adjust levels
    +    hospital,
    +    n = 2,                                          # keep top 2 levels
    +    other_level = "Other Hospital")) %>%            # all others as "Other Hospital"
    +  tabyl(hospital)                                   # print table
    +
    +
           hospital    n   percent
    +        Missing 1469 0.2494905
    +  Port Hospital 1762 0.2992527
    + Other Hospital 2657 0.4512568
    +
    +
    +
    +
    +
    +

    11.7 Show all levels

    +

    One benefit of using factors is to standardise the appearance of plot legends and tables, regardless of which values are actually present in a dataset.

    +

    If you are preparing many figures (e.g. for multiple jurisdictions) you will want the legends and tables to appear identically even with varying levels of data completion or data composition.

    +
    +

    In plots

    +

    In a ggplot() figure, simply add the argument drop = FALSE in the relevant scale_xxxx() function. All factor levels will be displayed, regardless of whether they are present in the data. If your factor column levels are displayed using fill =, then in scale_fill_discrete() you include drop = FALSE, as shown below. If your levels are displayed with x = (to the x-axis) color = or size = you would provide this to scale_color_discrete() or scale_size_discrete() accordingly.

    +

    This example is a stacked bar plot of age category, by hospital. Adding scale_fill_discrete(drop = FALSE) ensures that all age groups appear in the legend, even if not present in the data.

    +
    +
    ggplot(data = linelist) +
    +  geom_bar(mapping = aes(x = hospital, fill = age_cat)) +
    +  scale_fill_discrete(drop = FALSE) +                        # show all age groups in the legend, even those not present
    +  labs(
    +    title = "All age groups will appear in legend, even if not present in data")
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    In tables

    +

    Both the base R table() and tabyl() from janitor will show all factor levels (even unused levels).

    +

    If you use count() or summarise() from dplyr to make a table, add the argument .drop = FALSE to include counts for all factor levels even those unused.

    +

    Read more in the Descriptive tables page, or at the scale_discrete documentation, or the count() documentation. You can see another example in the Contact tracing page.

    +
    +
    +
    +

    11.8 Epiweeks

    +

    Please see the extensive discussion of how to create epidemiological weeks in the Grouping data page. Also see the Working with dates page for tips on how to create and format epidemiological weeks.

    +
    +

    Epiweeks in a plot

    +

    If your goal is to create epiweeks to display in a plot, you can do this simply with lubridate’s floor_date(), as explained in the Grouping data page. The values returned will be of class Date with format YYYY-MM-DD. If you use this column in a plot, the dates will naturally order correctly, and you do not need to worry about levels or converting to class Factor. See the ggplot() histogram of onset dates below.

    +

    In this approach, you can adjust the display of the dates on an axis with scale_x_date(). See the page on Epidemic curves for more information. You can specify a “strptime” display format to the date_labels = argument of scale_x_date(). These formats use “%” placeholders and are covered in the Working with dates page. Use “%Y” to represent a 4-digit year, and either “%W” or “%U” to represent the week number (Monday or Sunday weeks respectively).

    +
    +
    linelist %>% 
    +  mutate(epiweek_date = floor_date(date_onset, "week")) %>%  # create week column
    +  ggplot() +                                                  # begin ggplot
    +  geom_histogram(mapping = aes(x = epiweek_date)) +           # histogram of date of onset
    +  scale_x_date(date_labels = "%Y-W%W")                       # adjust disply of dates to be YYYY-WWw
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    Epiweeks in the data

    +

    However, if your purpose in factoring is not to plot, you can approach this one of two ways:

    +
      +
    1. For fine control over the display, convert the lubridate epiweek column (YYYY-MM-DD) to the desired display format (YYYY-Www) within the data frame itself, and then convert it to class Factor.
    2. +
    +

    First, use format() from base R to convert the date display from YYYY-MM-DD to YYYY-Www display (see the Working with dates page). In this process the class will be converted to character. Then, convert from character to class Factor with factor().

    +
    +
    linelist <- linelist %>% 
    +  mutate(epiweek_date = floor_date(date_onset, "week"),       # create epiweeks (YYYY-MM-DD)
    +         epiweek_formatted = format(epiweek_date, "%Y-W%W"),  # Convert to display (YYYY-WWw)
    +         epiweek_formatted = factor(epiweek_formatted))       # Convert to factor
    +
    +# Display levels
    +levels(linelist$epiweek_formatted)
    +
    +
     [1] "2014-W13" "2014-W14" "2014-W15" "2014-W16" "2014-W17" "2014-W18"
    + [7] "2014-W19" "2014-W20" "2014-W21" "2014-W22" "2014-W23" "2014-W24"
    +[13] "2014-W25" "2014-W26" "2014-W27" "2014-W28" "2014-W29" "2014-W30"
    +[19] "2014-W31" "2014-W32" "2014-W33" "2014-W34" "2014-W35" "2014-W36"
    +[25] "2014-W37" "2014-W38" "2014-W39" "2014-W40" "2014-W41" "2014-W42"
    +[31] "2014-W43" "2014-W44" "2014-W45" "2014-W46" "2014-W47" "2014-W48"
    +[37] "2014-W49" "2014-W50" "2014-W51" "2015-W00" "2015-W01" "2015-W02"
    +[43] "2015-W03" "2015-W04" "2015-W05" "2015-W06" "2015-W07" "2015-W08"
    +[49] "2015-W09" "2015-W10" "2015-W11" "2015-W12" "2015-W13" "2015-W14"
    +[55] "2015-W15" "2015-W16"
    +
    +
    +

    DANGER: If you place the weeks ahead of the years (“Www-YYYY”) (“%W-%Y”), the default alpha-numeric level ordering will be incorrect (e.g. 01-2015 will be before 35-2014). You could need to manually adjust the order, which would be a long painful process.

    +
      +
    1. For fast default display, use the aweek package and it’s function date2week(). You can set the week_start = day, and if you set factor = TRUE then the output column is an ordered factor. As a bonus, the factor includes levels for all possible weeks in the span - even if there are no cases that week.
    2. +
    +
    +
    df <- linelist %>% 
    +  mutate(epiweek = date2week(date_onset, week_start = "Monday", factor = TRUE))
    +
    +levels(df$epiweek)
    +
    +

    See the Working with dates page for more information about aweek. It also offers the reverse function week2date().

    + +
    +
    +
    +

    11.9 Resources

    +

    R for Data Science page on factors
    +aweek package vignette

    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/factors.qmd b/new_pages/factors.qmd index cd88dee5..b94408da 100644 --- a/new_pages/factors.qmd +++ b/new_pages/factors.qmd @@ -39,7 +39,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -84,7 +84,7 @@ table(linelist$delay_cat, useNA = "always") Likewise, if we make a bar plot, the values also appear in this order on the x-axis (see the [ggplot basics](ggplot_basics.qmd) page for more on **ggplot2** - the most common visualization package in R). ```{r, warning=F, message=F} -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_bar(mapping = aes(x = delay_cat)) ``` @@ -109,7 +109,7 @@ linelist <- linelist %>% levels(linelist$delay_cat) ``` -The function `fct_relevel()` has the additional utility of allowing you to manually specify the level order. Simply write the level values in order, in quotation marks, separated by commas, as shown below. Note that the spelling must exactly match the values. If you want to create levels that do not exist in the data, use [`fct_expand()` instead](#fct_add)). +The function `fct_relevel()` has the additional utility of allowing you to manually specify the level order. Simply write the level values in order, in quotation marks, separated by commas, as shown below. Note that the spelling must exactly match the values. If you want to create levels that do not exist in the data, use [`fct_expand()` instead](#fct_add). ```{r} linelist <- linelist %>% @@ -125,7 +125,7 @@ levels(linelist$delay_cat) Now the plot order makes more intuitive sense as well. ```{r, warning=F, message=F} -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_bar(mapping = aes(x = delay_cat)) ``` @@ -164,8 +164,8 @@ The package **forcats** offers useful functions to easily adjust the order of a These functions can be applied to a factor column in two contexts: -1) To the column in the data frame, as usual, so the transformation is available for any subsequent use of the data -2) *Inside of a plot*, so that the change is applied only within the plot +1) To the column in the data frame, as usual, so the transformation is available for any subsequent use of the data. +2) *Inside of a plot*, so that the change is applied only within the plot. @@ -175,8 +175,8 @@ This function is used to manually order the factor levels. If used on a non-fact Within the parentheses first provide the factor column name, then provide either: -* All the levels in the desired order (as a character vector `c()`), or -* One level and it's corrected placement using the `after = ` argument +* All the levels in the desired order (as a character vector `c()`), or, +* One level and it's corrected placement using the `after = ` argument. Here is an example of redefining the column `delay_cat` (which is already class Factor) and specifying all the desired order of levels. @@ -214,11 +214,11 @@ linelist <- linelist %>% ```{r, warning=F, message=F, out.width = c('50%', '50%'), fig.show='hold'} # Alpha-numeric default order - no adjustment within ggplot -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_bar(mapping = aes(x = delay_cat)) # Factor level order adjusted within ggplot -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_bar(mapping = aes(x = fct_relevel(delay_cat, c("<2 days", "2-5 days", ">5 days")))) ``` @@ -244,14 +244,14 @@ This function can be used within a `ggplot()`, as shown below. ```{r, out.width = c('50%', '50%', '50%'), fig.show='hold', warning=F, message=F} # ordered by frequency -ggplot(data = linelist, aes(x = fct_infreq(delay_cat)))+ - geom_bar()+ +ggplot(data = linelist, aes(x = fct_infreq(delay_cat))) + + geom_bar() + labs(x = "Delay onset to admission (days)", title = "Ordered by frequency") # reversed frequency -ggplot(data = linelist, aes(x = fct_rev(fct_infreq(delay_cat))))+ - geom_bar()+ +ggplot(data = linelist, aes(x = fct_rev(fct_infreq(delay_cat)))) + + geom_bar() + labs(x = "Delay onset to admission (days)", title = "Reverse of order by frequency") ``` @@ -272,26 +272,26 @@ In the first example below, the default order alpha-numeric level order is used. ```{r, fig.show='hold', message=FALSE, warning=FALSE, out.width=c('50%', '50%')} # boxplots ordered by original factor levels -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_boxplot( aes(x = delay_cat, y = ct_blood, - fill = delay_cat))+ + fill = delay_cat)) + labs(x = "Delay onset to admission (days)", - title = "Ordered by original alpha-numeric levels")+ - theme_classic()+ + title = "Ordered by original alpha-numeric levels") + + theme_classic() + theme(legend.position = "none") # boxplots ordered by median CT value -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_boxplot( aes(x = fct_reorder(delay_cat, ct_blood, "median"), y = ct_blood, - fill = delay_cat))+ + fill = delay_cat)) + labs(x = "Delay onset to admission (days)", - title = "Ordered by median CT value in group")+ - theme_classic()+ + title = "Ordered by median CT value in group") + + theme_classic() + theme(legend.position = "none") ``` @@ -312,12 +312,12 @@ epidemic_data <- linelist %>% # begin with the linelist hospital ) -ggplot(data = epidemic_data)+ # start plot +ggplot(data = epidemic_data) + # start plot geom_line( # make lines aes( x = epiweek, # x-axis epiweek y = n, # height is number of cases per week - color = fct_reorder2(hospital, epiweek, n)))+ # data grouped and colored by hospital, with factor order by height at end of plot + color = fct_reorder2(hospital, epiweek, n))) + # data grouped and colored by hospital, with factor order by height at end of plot labs(title = "Factor levels (and legend display) by line height at end of plot", color = "Hospital") # change legend title ``` @@ -350,7 +350,7 @@ You can adjust the level displays manually manually with `fct_recode()`. This is This tool can also be used to "combine" levels, by assigning multiple levels the same re-coded value. Just be careful to not lose information! Consider doing these combining steps in a new column (not over-writing the existing column). -`fct_recode()` has a different syntax than `recode()`. `recode()` uses `OLD = NEW`, whereas `fct_recode()` uses `NEW = OLD`. +**_DANGER:_** `fct_recode()` has a different syntax than `recode()`. `recode()` uses `OLD = NEW`, whereas `fct_recode()` uses `NEW = OLD`. The current levels of `delay_cat` are: ```{r, echo=F} @@ -444,10 +444,10 @@ In a `ggplot()` figure, simply add the argument `drop = FALSE` in the relevant ` This example is a stacked bar plot of age category, by hospital. Adding `scale_fill_discrete(drop = FALSE)` ensures that all age groups appear in the legend, even if not present in the data. -```{r} -ggplot(data = linelist)+ +```{r, fig.width = 10.5} +ggplot(data = linelist) + geom_bar(mapping = aes(x = hospital, fill = age_cat)) + - scale_fill_discrete(drop = FALSE)+ # show all age groups in the legend, even those not present + scale_fill_discrete(drop = FALSE) + # show all age groups in the legend, even those not present labs( title = "All age groups will appear in legend, even if not present in data") ``` @@ -463,8 +463,7 @@ Read more in the [Descriptive tables](tables_descriptive.qmd) page, or at the [s ## Epiweeks -Please see the extensive discussion of how to create epidemiological weeks in the [Grouping data](grouping.qmd) page. -Please also see the [Working with dates](dates.qmd) page for tips on how to create and format epidemiological weeks. +Please see the extensive discussion of how to create epidemiological weeks in the [Grouping data](grouping.qmd) page. Also see the [Working with dates](dates.qmd) page for tips on how to create and format epidemiological weeks. ### Epiweeks in a plot {.unnumbered} @@ -476,8 +475,8 @@ In this approach, you can adjust the *display* of the dates on an axis with `sca ```{r, warning=F, message=F} linelist %>% mutate(epiweek_date = floor_date(date_onset, "week")) %>% # create week column - ggplot()+ # begin ggplot - geom_histogram(mapping = aes(x = epiweek_date))+ # histogram of date of onset + ggplot() + # begin ggplot + geom_histogram(mapping = aes(x = epiweek_date)) + # histogram of date of onset scale_x_date(date_labels = "%Y-W%W") # adjust disply of dates to be YYYY-WWw ``` @@ -486,7 +485,7 @@ linelist %>% However, if your purpose in factoring is *not* to plot, you can approach this one of two ways: -1) *For fine control over the display*, convert the **lubridate** epiweek column (YYYY-MM-DD) to the desired display format (YYYY-WWw) *within the data frame itself*, and then convert it to class Factor. +1) *For fine control over the display*, convert the **lubridate** epiweek column (YYYY-MM-DD) to the desired display format (YYYY-Www) *within the data frame itself*, and then convert it to class Factor. First, use `format()` from **base** R to convert the date display from YYYY-MM-DD to YYYY-Www display (see the [Working with dates](dates.qmd) page). In this process the class will be converted to character. Then, convert from character to class Factor with `factor()`. @@ -519,5 +518,6 @@ See the [Working with dates](dates.qmd) page for more information about **aweek* ## Resources {} -R for Data Science page on [factors](https://r4ds.had.co.nz/factors.html) +R for Data Science page on [factors](https://r4ds.had.co.nz/factors.html) + [aweek package vignette](https://cran.r-project.org/web/packages/aweek/vignettes/introduction.html) diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-12-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-12-1.png new file mode 100644 index 00000000..3357f8f7 Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-12-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-18-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-18-1.png new file mode 100644 index 00000000..f0f7aeb8 Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-18-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-18-2.png b/new_pages/factors_files/figure-html/unnamed-chunk-18-2.png new file mode 100644 index 00000000..d1b7f211 Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-18-2.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-19-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-19-1.png new file mode 100644 index 00000000..fc4a23ea Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-19-2.png b/new_pages/factors_files/figure-html/unnamed-chunk-19-2.png new file mode 100644 index 00000000..ea8cb26d Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-19-2.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-20-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-20-1.png new file mode 100644 index 00000000..d8ec035b Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-20-2.png b/new_pages/factors_files/figure-html/unnamed-chunk-20-2.png new file mode 100644 index 00000000..b6c7768b Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-20-2.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-21-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-21-1.png new file mode 100644 index 00000000..427e9202 Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-21-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-29-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-29-1.png new file mode 100644 index 00000000..f913527c Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-29-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-30-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-30-1.png new file mode 100644 index 00000000..223af729 Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-30-1.png differ diff --git a/new_pages/factors_files/figure-html/unnamed-chunk-7-1.png b/new_pages/factors_files/figure-html/unnamed-chunk-7-1.png new file mode 100644 index 00000000..c585d0d1 Binary files /dev/null and b/new_pages/factors_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/new_pages/flexdashboard.qmd b/new_pages/flexdashboard.qmd index baf4fd57..75a37eaa 100644 --- a/new_pages/flexdashboard.qmd +++ b/new_pages/flexdashboard.qmd @@ -5,20 +5,20 @@ knitr::include_graphics(here::here("images", "flexdashboard_output.png")) ``` -This page will cover the basic use of the **flexdashboard** package. This package allows you to easily format R Markdown output as a dashboard with panels and pages. The dashboard content can be text, static figures/tables or interactive graphics. +This page will cover the basic use of the [**flexdashboard** package](https://pkgs.rstudio.com/flexdashboard/). This package allows you to easily format R Markdown output as a dashboard with panels and pages. The dashboard content can be text, static figures/tables or interactive graphics. Advantages of **flexdashboard**: -* It requires minimal non-standard R coding - with very little practice you can quickly create a dashboard -* The dashboard can usually be emailed to colleagues as a self-contained HTML file - no server required -* You can combine **flexdashboard** with **shiny**, **ggplotly**, and other *"html widgets"* to add interactivity +* It requires minimal non-standard R coding - with very little practice you can quickly create a dashboard. +* The dashboard can usually be emailed to colleagues as a self-contained HTML file - no server required. +* You can combine **flexdashboard** with [**shiny**](shiny_basics.qmd), [**ggplotly**](interactive_plots.qmd), and other *"html widgets"* to add interactivity. Disadvantages of **flexdashboard**: -* Less customization as compared to using **shiny** alone to create a dashboard +* Less customization as compared to using **shiny** alone to create a dashboard. -Very comprehensive tutorials on using **flexdashboard** that informed this page can be found in the Resources section. Below we describe the core features and give an example of building a dashboard to explore an outbreak, using the case `linelist` data. +Very comprehensive tutorials on using **flexdashboard** that informed this page can be found in the [Resources](flexdashboard.qmd#resources) section. Below we describe the core features and give an example of building a dashboard to explore an outbreak, using the case `linelist` data. ## Preparation @@ -27,7 +27,7 @@ Very comprehensive tutorials on using **flexdashboard** that informed this page In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. -```{r} +```{r, warning=F, message=F} pacman::p_load( rio, # data import/export here, # locate files @@ -42,7 +42,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -159,8 +159,8 @@ Section attributes specific to **flexdashboard** include: * `{data-orientation=}` Set to either `rows` or `columns`. If your dashboard has multiple pages, add this attribute to each page to indicate orientation (further explained in [layout section](#layout)). * `{data-width=}` and `{data-height=}` set relative size of charts, columns, rows laid out in the same dimension (horizontal or vertical). Absolute sizes are adjusted to best fill the space on any display device thanks to the [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes) engine. * Height of charts also depends on whether you set the YAML parameter `vertical_layout: fill` or `vertical_layout: scroll`. If set to scroll, figure height will reflect the traditional `fig.height = ` option in the R code chunk. - * See complete size documentation at the [flexdashboard website](https://rmarkdown.rstudio.com/flexdashboard/using.html#sizing) -* `{.hidden}` Use this to exclude a specific page from the navigation bar + * See complete size documentation at the [flexdashboard website](https://pkgs.rstudio.com/flexdashboard/articles/using.html#sizing) +* `{.hidden}` Use this to exclude a specific page from the navigation bar. * `{data-navbar=}` Use this in a page-level heading to nest it within a navigation bar drop-down menu. Provide the name (in quotes) of the drop-down menu. See example below. @@ -168,10 +168,10 @@ Section attributes specific to **flexdashboard** include: Adjust the layout of your dashboard in the following ways: -* Add pages, columns/rows, and charts with R Markdown headings (e.g. #, ##, or ###) -* Adjust the YAML parameter `orientation:` to either `rows` or `columns` -* Specify whether the layout fills the browser or allows scrolling -* Add tabs to a particular section heading +* Add pages, columns/rows, and charts with R Markdown headings (e.g. #, ##, or ###). +* Adjust the YAML parameter `orientation:` to either `rows` or `columns`. +* Specify whether the layout fills the browser or allows scrolling. +* Add tabs to a particular section heading. ### Pages {.unnumbered} @@ -314,7 +314,7 @@ DT::datatable(linelist, ### Plots {.unnumbered} -You can print plots to a dashboard pane as you would in an R script. In our example, we use the **incidence2** package to create an "epicurve" by age group with two simple commands (see [Epidemic curves](epicurves.qmd) page). However, you could use `ggplot()` and print a plot in the same manner. +You can print plots to a dashboard pane as you would in an R script. In our example, we use the **incidence2** package to create an "epicurve" by age group with two simple commands (see [An Introduction to incidence2](https://www.reconverse.org/incidence2/articles/incidence2.html) page). However, you could use `ggplot()` and print a plot in the same manner. ```{r, out.width = c('100%'), out.height = c('100%'), echo=F} knitr::include_graphics(here::here("images", "flexdashboard_plots_script.png")) @@ -349,11 +349,11 @@ knitr::include_graphics(here::here("images", "flexdashboard_ggplotly.gif")) Some common examples of these widgets include: -* Plotly (used in this handbook page and in the [Interative plots](interactive_plots.qmd) page) -* visNetwork (used in the [Transmission Chains](transmission_chains.qmd) page of this handbook) -* Leaflet (used in the [GIS Basics](gis.qmd) page of this handbook) -* dygraphs (useful for interactively showing time series data) -* DT (`datatable()`) (used to show dynamic tables with filter, sort, etc.) +* Plotly (used in this handbook page and in the [Interative plots](interactive_plots.qmd) page). +* visNetwork (used in the [Transmission Chains](transmission_chains.qmd) page of this handbook). +* Leaflet (used in the [GIS Basics](gis.qmd) page of this handbook). +* dygraphs (useful for interactively showing time series data). +* DT (`datatable()`) (used to show dynamic tables with filter, sort, etc.). Below we demonstrate adding an epidemic transmission chain which uses visNetwork to the dashboard. The script shows only the new code added to the "Column 2" section of the R Markdown script. You can find the code in the [Transmission chains](transmission_chains.qmd) page of this handbook. @@ -385,7 +385,7 @@ Embedding **shiny** in **flexdashboard** is however, a fundamental change to you Sharing your dashboard will now require that you either: * Send the Rmd script to the viewer, they open it in R on their computer, and run the app, or -* The app/dashboard is hosted on a server accessible to the viewer +* the app/dashboard is hosted on a server accessible to the viewer. Thus, there are benefits to integrating **shiny**, but also complications. If easy sharing by email is a priority and you don't need **shiny** reactive capabilities, consider the reduced interactivity offered by `ggplotly()` as demonstrated above. @@ -417,14 +417,14 @@ If your app/dashboard is hosted on a server and may have multiple simultaneous u Here we adapt the flexdashboard script "outbreak_dashboard.Rmd" to include **shiny**. We will add the capability for the user to select a hospital from a drop-down menu, and have the epidemic curve reflect only cases from that hospital, with a dynamic plot title. We do the following: -* Add `runtime: shiny` to the YAML -* Re-name the setup chunk as `global` +* Add `runtime: shiny` to the YAML. +* Re-name the setup chunk as `global`. * Create a sidebar containing: - * Code to create a vector of unique hospital names - * A `selectInput()` command (**shiny** drop-down menu) with the choice of hospital names. The selection is saved as `hospital_choice`, which can be referenced in later code as `input$hospital_choice` + * Code to create a vector of unique hospital names. + * A `selectInput()` command (**shiny** drop-down menu) with the choice of hospital names. The selection is saved as `hospital_choice`, which can be referenced in later code as `input$hospital_choice`. * The epidemic curve code (column 2) is wrapped within `renderPlot({ })`, including: - * A filter on the dataset restricting the column `hospital` to the current value of `input$hospital_choice` - * A dynamic plot title that incorporates `input$hospital_choice` + * A filter on the dataset restricting the column `hospital` to the current value of `input$hospital_choice`. + * A dynamic plot title that incorporates `input$hospital_choice`. Note that any code referencing an `input$` value must be within a `render({})` function (to be reactive). @@ -467,10 +467,8 @@ If you have embedded **shiny**, you will not be able to send an output by email, Excellent tutorials that informed this page can be found below. If you review these, most likely within an hour you can have your own dashboard. -https://bookdown.org/yihui/rmarkdown/dashboards.html +[Rmarkdown and Dashboards](https://bookdown.org/yihui/rmarkdown/dashboards.html) -https://rmarkdown.rstudio.com/flexdashboard/ +[Flexdashboard](https://rmarkdown.rstudio.com/flexdashboard/) -https://rmarkdown.rstudio.com/flexdashboard/using.html - -https://rmarkdown.rstudio.com/flexdashboard/examples.html +[Flexdashboard gallery](https://rmarkdown.rstudio.com/flexdashboard/examples.html) diff --git a/new_pages/ggplot_basics.qmd b/new_pages/ggplot_basics.qmd index 2297d4cd..102efef4 100644 --- a/new_pages/ggplot_basics.qmd +++ b/new_pages/ggplot_basics.qmd @@ -12,7 +12,7 @@ The syntax is significantly different from **base** `R` plotting, and has a lear In this page we will cover the fundamentals of plotting with **ggplot2**. See the page [ggplot tips](ggplot_tips.qmd) for suggestions and advanced techniques to make your plots really look nice. -There are several extensive **ggplot2** tutorials linked in the resources section. You can also download this [data visualization with ggplot cheatsheet](https://github.com/rstudio/cheatsheets/raw/master/data-visualization-2.1.pdf) from the RStudio website. If you want inspiration for ways to creatively visualise your data, we suggest reviewing websites like the [R graph gallery](https://www.r-graph-gallery.com/) and [Data-to-viz](https://www.data-to-viz.com/caveats.html). +There are several extensive **ggplot2** tutorials linked in the resources section. You can also download this [data visualization with ggplot cheatsheet](https://rstudio.github.io/cheatsheets/data-visualization.pdf) from the RStudio website. If you want inspiration for ways to creatively visualise your data, we suggest reviewing websites like the [R graph gallery](https://www.r-graph-gallery.com/) and [Data-to-viz](https://www.data-to-viz.com/caveats.html). @@ -38,7 +38,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -62,9 +62,9 @@ When preparing data to plot, it is best to make the data adhere to ["tidy" data Some simple ways we can prepare our data to make it better for plotting can include making the contents of the data better for display - which does not necessarily equate to better for data manipulation. For example: -* Replace `NA` values in a character column with the character string "Unknown" -* Consider converting column to class *factor* so their values have prescribed ordinal levels -* Clean some columns so that their "data friendly" values with underscores etc are changed to normal text or title case (see [Characters and strings](characters_strings.qmd)) +* Replace `NA` values in a character column with the character string "Unknown". +* Consider converting column to class *factor* so their values have prescribed ordinal levels. +* Clean some columns so that their "data friendly" values with underscores etc are changed to normal text or title case (see [Characters and strings](characters_strings.qmd)). Here are some examples of this in action: @@ -139,19 +139,19 @@ Plotting with **ggplot2** is based on "adding" plot layers and design elements o ggplot objects can be highly complex, but the basic order of layers will usually look like this: -1. Begin with the baseline `ggplot()` command - this "opens" the ggplot and allow subsequent functions to be added with `+`. Typically the dataset is also specified in this command +1. Begin with the baseline `ggplot()` command - this "opens" the ggplot and allow subsequent functions to be added with `+`. Typically the dataset is also specified in this command. 2. Add "geom" layers - these functions visualize the data as *geometries* (*shapes*), e.g. as a bar graph, line plot, scatter plot, histogram (or a combination!). These functions all start with `geom_` as a prefix. -3. Add design elements to the plot such as axis labels, title, fonts, sizes, color schemes, legends, or axes rotation +3. Add design elements to the plot such as axis labels, title, fonts, sizes, color schemes, legends, or axes rotation. A simple example of skeleton code is as follows. We will explain each component in the sections below. ```{r, eval=F} # plot data from my_data columns as red points -ggplot(data = my_data)+ # use the dataset "my_data" +ggplot(data = my_data) + # use the dataset "my_data" geom_point( # add a layer of points (dots) mapping = aes(x = col1, y = col2), # "map" data column to axes - color = "red")+ # other specification for the geom - labs()+ # here you add titles, axes labels, etc. + color = "red") + # other specification for the geom + labs() + # here you add titles, axes labels, etc. theme() # here you adjust color, font, size etc of non-data plot elements (axes, title, etc.) ``` @@ -200,14 +200,16 @@ Below, in the `ggplot()` command the data are set as the case `linelist`. In the After a `+`, the plotting commands continue. A shape is created with the "geom" function `geom_point()`. This geom *inherits* the mappings from the `ggplot()` command above - it knows the axis-column assignments and proceeds to visualize those relationships as *points* on the canvas. ```{r, warning=F, message=F} -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + geom_point() ``` As another example, the following commands utilize the same data, a slightly different mapping, and a different geom. The `geom_histogram()` function only requires a column mapped to the x-axis, as the counts y-axis is generated automatically. ```{r, warning=F, message=F} -ggplot(data = linelist, mapping = aes(x = age))+ +ggplot(data = linelist, + mapping = aes(x = age)) + geom_histogram() ``` @@ -218,19 +220,19 @@ In ggplot terminology a plot "aesthetic" has a specific meaning. It refers to a Therefore, plot object *aesthetics* can be colors, sizes, transparencies, placement, etc. *of the plotted data*. Not all geoms will have the same aesthetic options, but many can be used by most geoms. Here are some examples: -* `shape =` Display a point with `geom_point()` as a dot, star, triangle, or square... -* `fill = ` The interior color (e.g. of a bar or boxplot) -* `color =` The exterior line of a bar, boxplot, etc., or the point color if using `geom_point()` -* `size = ` Size (e.g. line thickness, point size) -* `alpha = ` Transparency (1 = opaque, 0 = invisible) -* `binwidth = ` Width of histogram bins -* `width = ` Width of "bar plot" columns -* `linetype =` Line type (e.g. solid, dashed, dotted) +* `shape =` Display a point with `geom_point()` as a dot, star, triangle, or square, etc. +* `fill = ` The interior color (e.g. of a bar or boxplot). +* `color =` The exterior line of a bar, boxplot, etc., or the point color if using `geom_point()`. +* `size = ` Size (e.g. line thickness, point size). +* `alpha = ` Transparency (1 = opaque, 0 = invisible). +* `binwidth = ` Width of histogram bins. +* `width = ` Width of "bar plot" columns. +* `linetype =` Line type (e.g. solid, dashed, dotted). These plot object aesthetics can be assigned values in two ways: -1) Assigned a static value (e.g. `color = "blue"`) to apply across all plotted observations -2) Assigned to a column of the data (e.g. `color = hospital`) such that display of each observation depends on its value in that column +1) Assigned a static value (e.g. `color = "blue"`) to apply across all plotted observations. +2) Assigned to a column of the data (e.g. `color = hospital`) such that display of each observation depends on its value in that column. @@ -243,11 +245,13 @@ If you want the plot object aesthetic to be static, that is - to be the same for ```{r, out.width=c('50%', '50%'), fig.show='hold', warning=F, message=F} # scatterplot -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ # set data and axes mapping +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + # set data and axes mapping geom_point(color = "darkgreen", size = 0.5, alpha = 0.2) # set static point aesthetics # histogram -ggplot(data = linelist, mapping = aes(x = age))+ # set data and axes +ggplot(data = linelist, + mapping = aes(x = age)) + # set data and axes geom_histogram( # display histogram binwidth = 7, # width of bins color = "red", # bin line color @@ -260,7 +264,7 @@ ggplot(data = linelist, mapping = aes(x = age))+ # set data and axes The alternative is to scale the plot object aesthetic by the values in a column. In this approach, the display of this aesthetic will depend on that observation's value in that column of the data. If the column values are continuous, the display scale (legend) for that aesthetic will be continuous. If the column values are discrete, the legend will display each value and the plotted data will appear as distinctly "grouped" (read more in the [grouping](#ggplotgroups) section of this page). -To achieve this, you map that plot aesthetic to a *column name* (not in quotes). This must be done *within a `mapping = aes()` function* (note: there are several places in the code you can make these mapping assignments, as discussed [below](##ggplot_basics_map_loc)). +To achieve this, you map that plot aesthetic to a *column name* (not in quotes). This must be done *within a `mapping = aes()` function* (note: there are several places in the code you can make these mapping assignments, as discussed below. Two examples are below. @@ -274,7 +278,7 @@ ggplot(data = linelist, # set data x = age, # map x-axis to age y = wt_kg, # map y-axis to weight color = age) - )+ # map color to age + ) + # map color to age geom_point() # display data as points # scatterplot @@ -283,7 +287,7 @@ ggplot(data = linelist, # set data x = age, # map x-axis to age y = wt_kg, # map y-axis to weight color = age, # map color to age - size = age))+ # map size to age + size = age)) + # map size to age geom_point( # display data as points shape = "diamond", # points display as diamonds alpha = 0.3) # point transparency at 30% @@ -323,8 +327,8 @@ ggplot(data = linelist, Aesthetic mapping within `mapping = aes()` can be written in several places in your plotting commands and can even be written more than once. This can be written in the top `ggplot()` command, and/or for each individual geom beneath. The nuances include: -* Mapping assignments made in the top `ggplot()` command will be inherited as defaults across any geom below, like how `x = ` and `y = ` are inherited -* Mapping assignments made within one geom apply only to that geom +* Mapping assignments made in the top `ggplot()` command will be inherited as defaults across any geom below, like how `x = ` and `y = ` are inherited. +* Mapping assignments made within one geom apply only to that geom. Likewise, `data = ` specified in the top `ggplot()` will apply by default to any geom below, but you could also specify data for each geom (but this is more difficult). @@ -332,13 +336,14 @@ Thus, each of the following commands will create the same plot: ```{r, eval=F, warning=F, message=F} # These commands will produce the exact same plot -ggplot(data = linelist, mapping = aes(x = age))+ +ggplot(data = linelist, + mapping = aes(x = age)) + geom_histogram() -ggplot(data = linelist)+ +ggplot(data = linelist) + geom_histogram(mapping = aes(x = age)) -ggplot()+ +ggplot() + geom_histogram(data = linelist, mapping = aes(x = age)) ``` @@ -356,7 +361,7 @@ For example, if you want points to be displayed by gender, you would set `mappin ```{r, warning=F, message=F} ggplot(data = linelist, - mapping = aes(x = age, y = wt_kg, color = gender))+ + mapping = aes(x = age, y = wt_kg, color = gender)) + geom_point(alpha = 0.5) ``` @@ -364,7 +369,7 @@ ggplot(data = linelist, ```{r, eval=F} # This alternative code produces the same plot ggplot(data = linelist, - mapping = aes(x = age, y = wt_kg))+ + mapping = aes(x = age, y = wt_kg)) + geom_point( mapping = aes(color = gender), alpha = 0.5) @@ -384,7 +389,7 @@ To adjust the order of groups in a plot, see the [ggplot tips](ggplot_tips.qmd) Facets, or "small-multiples", are used to split one plot into a multi-panel figure, with one panel ("facet") per group of data. The same type of plot is created multiple times, each one using a sub-group of the same dataset. -Faceting is a functionality that comes with **ggplot2**, so the legends and axes of the facet "panels" are automatically aligned. There are other packages discussed in the [ggplot tips](ggplot_tips.qmd) page that are used to combine completely different plots (**cowplot** and **patchwork**) into one figure. +Faceting is a functionality that comes with **ggplot2**, so the legends and axes of the facet "panels" are automatically aligned. There are other packages discussed in the [ggplot tips](ggplot_tips.qmd) page that are used to combine completely different plots (with **patchwork**) into one figure. Faceting is done with one of the following **ggplot2** functions: @@ -392,7 +397,7 @@ Faceting is done with one of the following **ggplot2** functions: + You can invoke certain options to determine the layout of the facets, e.g. `nrow = 1` or `ncol = 1` to control the number of rows or columns that the faceted plots are arranged within. 2. `facet_grid()` This is used when you want to bring a second variable into the faceting arrangement. Here each panel of a grid shows the intersection between values in *two columns*. For example, epidemic curves for each hospital-age group combination with hospitals along the top (columns) and age groups along the sides (rows). - + `nrow` and `ncol` are not relevant, as the subgroups are presented in a grid + + `nrow` and `ncol` are not relevant, as the subgroups are presented in a grid. Each of these functions accept a formula syntax to specify the column(s) for faceting. Both accept up to two columns, one on each side of a tilde `~`. @@ -427,9 +432,10 @@ When we add the command `facet_wrap()`, we specify a tilde and then the column t ```{r, warning=F, message=F} # A plot with facets by district -ggplot(malaria_data, aes(x = data_date, y = malaria_tot)) + +ggplot(malaria_data, + mapping = aes(x = data_date, y = malaria_tot)) + geom_col(width = 1, fill = "darkred") + # plot the count data as columns - theme_minimal()+ # simplify the background panels + theme_minimal() + # simplify the background panels labs( # add plot labels, title, etc. x = "Date of report", y = "Malaria cases", @@ -464,9 +470,10 @@ DT::datatable(head(malaria_age, 50), rownames = FALSE, filter="top", options = l When you pass the two variables to `facet_grid()`, easiest is to use formula notation (e.g. `x ~ y`) where x is rows and y is columns. Here is the plot, using `facet_grid()` to show the plots for each combination of the columns `age_group` and `District`. ```{r, message=F, warning=F} -ggplot(malaria_age, aes(x = data_date, y = num_cases)) + +ggplot(malaria_age, + mapping = aes(x = data_date, y = num_cases)) + geom_col(fill = "darkred", width = 1) + - theme_minimal()+ + theme_minimal() + labs( x = "Date of report", y = "Malaria cases", @@ -486,9 +493,10 @@ When using `facet_grid` only, we can add `space = "free_y"` or `space = "free_x" ```{r, message=FALSE, warning=FALSE} # Free y-axis -ggplot(malaria_data, aes(x = data_date, y = malaria_tot)) + +ggplot(malaria_data, + mapping = aes(x = data_date, y = malaria_tot)) + geom_col(width = 1, fill = "darkred") + # plot the count data as columns - theme_minimal()+ # simplify the background panels + theme_minimal() + # simplify the background panels labs( # add plot labels, title, etc. x = "Date of report", y = "Malaria cases", @@ -500,28 +508,23 @@ ggplot(malaria_data, aes(x = data_date, y = malaria_tot)) + - + - + - + - + -### Factor level order in facets {.unnumbered} - -See this [post](https://juliasilge.com/blog/reorder-within/) on how to re-order factor levels *within* facets. - - ## Storing plots ### Saving plots {.unnumbered} @@ -530,7 +533,8 @@ By default when you run a `ggplot()` command, the plot will be printed to the Pl ```{r, warning=F, message=F} # define plot -age_by_wt <- ggplot(data = linelist, mapping = aes(x = age_years, y = wt_kg, color = age_years))+ +age_by_wt <- ggplot(data = linelist, + mapping = aes(x = age_years, y = wt_kg, color = age_years)) + geom_point(alpha = 0.1) # print @@ -545,7 +549,7 @@ One nice thing about **ggplot2** is that you can define a plot (as above), and t For example, to modify the plot `age_by_wt` that was defined above, to include a vertical line at age 50, we would just add a `+` and begin adding additional layers to the plot. ```{r, warning=F, message=F} -age_by_wt+ +age_by_wt + geom_vline(xintercept = 50) ``` @@ -554,14 +558,14 @@ age_by_wt+ Exporting ggplots is made easy with the `ggsave()` function from **ggplot2**. It can work in two ways, either: -* Specify the name of the plot object, then the file path and name with extension - * For example: `ggsave(my_plot, here("plots", "my_plot.png"))` -* Run the command with only a file path, to save the last plot that was printed - * For example: `ggsave(here("plots", "my_plot.png"))` +* Specify the name of the plot object, then the file path and name with extension. + * For example: `ggsave(my_plot, here("plots", "my_plot.png"))`. +* Run the command with only a file path, to save the last plot that was printed. + * For example: `ggsave(here("plots", "my_plot.png"))`. You can export as png, pdf, jpeg, tiff, bmp, svg, or several other file types, by specifying the file extension in the file path. -You can also specify the arguments `width = `, `height = `, and `units = ` (either "in", "cm", or "mm"). You can also specify `dpi = ` with a number for plot resolution (e.g. 300). See the function details by entering `?ggsave` or reading the [documentation online](https://ggplot2.tidyverse.org/reference/ggsave.html). +You can also specify the arguments `width = `, `height = `, and `units = ` (either "in", "cm", or "mm"). You can also specify `dpi = ` with a number for plot resolution (e.g. 300). You can also change the the background of your plot by using the argument `bg = `, where you specify the colour, i.e. `bg = "white". See the function details by entering `?ggsave` or reading the [documentation online](https://ggplot2.tidyverse.org/reference/ggsave.html). Remember that you can use `here()` syntax to provide the desired file path. see the [Import and export](importing.qmd) page for more information. @@ -572,10 +576,10 @@ Surely you will want to add or adjust the plot's labels. These are most easily d Within `labs()` you can provide character strings to these arguements: -* `x = ` and `y = ` The x-axis and y-axis title (labels) -* `title = ` The main plot title -* `subtitle = ` The subtitle of the plot, in smaller text below the title -* `caption = ` The caption of the plot, in bottom-right by default +* `x = ` and `y = ` The x-axis and y-axis title (labels). +* `title = ` The main plot title. +* `subtitle = ` The subtitle of the plot, in smaller text below the title. +* `caption = ` The caption of the plot, in bottom-right by default. Here is a plot we made earlier, but with nicer labels: @@ -585,8 +589,8 @@ age_by_wt <- ggplot( mapping = aes( # map aesthetics to column values x = age, # map x-axis to age y = wt_kg, # map y-axis to weight - color = age))+ # map color to age - geom_point()+ # display data as points + color = age)) + # map color to age + geom_point() + # display data as points labs( title = "Age and weight distribution", subtitle = "Fictional Ebola outbreak, 2014", @@ -608,8 +612,8 @@ A note on specifying the *legend* title: There is no one "legend title" argument One of the best parts of **ggplot2** is the amount of control you have over the plot - you can define anything! As mentioned above, the design of the plot that is *not* related to the data shapes/geometries are adjusted within the `theme()` function. For example, the plot background color, presence/absence of gridlines, and the font/size/color/alignment of text (titles, subtitles, captions, axis text...). These adjustments can be done in one of two ways: -* Add a [*complete theme*](https://ggplot2.tidyverse.org/reference/ggtheme.html) `theme_()` function to make sweeping adjustments - these include `theme_classic()`, `theme_minimal()`, `theme_dark()`, `theme_light()` `theme_grey()`, `theme_bw()` among others -* Adjust each tiny aspect of the plot individually within `theme()` +* Add a [complete theme](https://ggplot2.tidyverse.org/reference/ggtheme.html). The function `theme_()` makes sweeping adjustments - these include: `theme_classic()`, `theme_minimal()`, `theme_dark()`, `theme_light()` `theme_grey()`, `theme_bw()` among others. +* Adjust each tiny aspect of the plot individually within `theme()`. ### Complete themes {.unnumbered} @@ -620,24 +624,28 @@ Write them with empty parentheses. ```{r, out.width=c('50%', '50%'), fig.show='hold', warning=F, message=F} -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(color = "darkgreen", size = 0.5, alpha = 0.2)+ - labs(title = "Theme classic")+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(color = "darkgreen", size = 0.5, alpha = 0.2) + + labs(title = "Theme classic") + theme_classic() -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(color = "darkgreen", size = 0.5, alpha = 0.2)+ - labs(title = "Theme bw")+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(color = "darkgreen", size = 0.5, alpha = 0.2) + + labs(title = "Theme bw") + theme_bw() -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(color = "darkgreen", size = 0.5, alpha = 0.2)+ - labs(title = "Theme minimal")+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(color = "darkgreen", size = 0.5, alpha = 0.2) + + labs(title = "Theme minimal") + theme_minimal() -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(color = "darkgreen", size = 0.5, alpha = 0.2)+ - labs(title = "Theme gray")+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(color = "darkgreen", size = 0.5, alpha = 0.2) + + labs(title = "Theme gray") + theme_gray() @@ -648,28 +656,28 @@ ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ The `theme()` function can take a large number of arguments, each of which edits a very specific aspect of the plot. There is no way we could cover all of the arguments, but we will describe the general pattern for them and show you how to find the argument name that you need. The basic syntax is this: -1. Within `theme()` write the argument name for the plot element you want to edit, like `plot.title = ` -3. Provide an `element_()` function to the argument - + Most often, use `element_text()`, but others include `element_rect()` for canvas background colors, or `element_blank()` to remove plot elements -4. Within the `element_()` function, write argument assignments to make the fine adjustments you desire +1. Within `theme()` write the argument name for the plot element you want to edit, like `plot.title = `. +2. Provide an `element_()` function to the argument. + * Most often, use `element_text()`, but others include `element_rect()` for canvas background colors, or `element_blank()` to remove plot elements. +3. Within the `element_()` function, write argument assignments to make the fine adjustments you desire. So, that description was quite abstract, so here are some examples. The below plot looks quite silly, but it serves to show you a variety of the ways you can adjust your plot. -* We begin with the plot `age_by_wt` defined just above and add `theme_classic()` -* For finer adjustments we add `theme()` and include one argument for each plot element to adjust +* We begin with the plot `age_by_wt` defined just above and add `theme_classic()`. +* For finer adjustments we add `theme()` and include one argument for each plot element to adjust. It can be nice to organize the arguments in logical sections. To describe just some of those used below: * `legend.position = ` is unique in that it accepts simple values like "bottom", "top", "left", and "right". But generally, text-related arguments require that you place the details *within* `element_text()`. -* Title size with `element_text(size = 30)` -* The caption horizontal alignment with `element_text(hjust = 0)` (from right to left) -* The subtitle is italicized with `element_text(face = "italic")` +* Title size with `element_text(size = 30)`. +* The caption horizontal alignment with `element_text(hjust = 0)` (from right to left). +* The subtitle is italicized with `element_text(face = "italic")`. ```{r, , warning=F, message=F} age_by_wt + - theme_classic()+ # pre-defined theme adjustments + theme_classic() + # pre-defined theme adjustments theme( legend.position = "bottom", # move legend to bottom @@ -738,7 +746,7 @@ linelist %>% # begin with li symptom_is_present = replace_na(symptom_is_present, "unknown")) %>% ggplot( # begin ggplot! - mapping = aes(x = symptom_name, fill = symptom_is_present))+ + mapping = aes(x = symptom_name, fill = symptom_is_present)) + geom_bar(position = "fill", col = "black") + theme_classic() + labs( @@ -767,7 +775,7 @@ Visualisations covered here include: * **Violin plot**, show the distribution of a continuous variable based on the symmetrical width of the 'violin'. * **Sina plot**, are a combination of jitter and violin plots, where individual points are shown but in the symmetrical shape of the distribution (via **ggforce** package). * **Scatter plot** for two continuous variables. -* **Heat plots** for three continuous variables (linked to [Heat plots](heatmaps.qmd) page) +* **Heat plots** for three continuous variables (linked to [Heat plots](heatmaps.qmd) page). @@ -789,24 +797,28 @@ If you do not want to specify a number of bins to `bins = `, you could alternati ```{r fig.show='hold', message=FALSE, warning=FALSE, out.width=c('50%', '50%')} # A) Regular histogram -ggplot(data = linelist, aes(x = age))+ # provide x variable - geom_histogram()+ +ggplot(data = linelist, + mapping = aes(x = age)) + # provide x variable + geom_histogram() + labs(title = "A) Default histogram (30 bins)") # B) More bins -ggplot(data = linelist, aes(x = age))+ # provide x variable - geom_histogram(bins = 50)+ +ggplot(data = linelist, + mapping = aes(x = age)) + # provide x variable + geom_histogram(bins = 50) + labs(title = "B) Set to 50 bins") # C) Fewer bins -ggplot(data = linelist, aes(x = age))+ # provide x variable - geom_histogram(bins = 5)+ +ggplot(data = linelist, + mapping = aes(x = age)) + # provide x variable + geom_histogram(bins = 5) + labs(title = "C) Set to 5 bins") # D) More bins -ggplot(data = linelist, aes(x = age))+ # provide x variable - geom_histogram(binwidth = 1)+ +ggplot(data = linelist, + mapping = aes(x = age)) + # provide x variable + geom_histogram(binwidth = 1) + labs(title = "D) binwidth of 1") ``` @@ -817,48 +829,54 @@ To get smoothed proportions, you can use `geom_density()`: ```{r, fig.show='hold', message=FALSE, warning=FALSE, out.width=c('50%', '50%')} # Frequency with proportion axis, smoothed -ggplot(data = linelist, mapping = aes(x = age)) + - geom_density(size = 2, alpha = 0.2)+ +ggplot(data = linelist, + mapping = aes(x = age)) + + geom_density(size = 2, alpha = 0.2) + labs(title = "Proportional density") # Stacked frequency with proportion axis, smoothed -ggplot(data = linelist, mapping = aes(x = age, fill = gender)) + - geom_density(size = 2, alpha = 0.2, position = "stack")+ +ggplot(data = linelist, + mapping = aes(x = age, fill = gender)) + + geom_density(size = 2, alpha = 0.2, position = "stack") + labs(title = "'Stacked' proportional densities") ``` To get a "stacked" histogram (of a continuous column of data), you can do one of the following: -1) Use `geom_histogram()` with the `fill = ` argument within `aes()` and assigned to the grouping column, or -2) Use `geom_freqpoly()`, which is likely easier to read (you can still set `binwidth = `) +1) Use `geom_histogram()` with the `fill = ` argument within `aes()` and assigned to the grouping column, or, +2) Use `geom_freqpoly()`, which is likely easier to read (you can still set `binwidth = `). 3) To see proportions of all values, set the `y = after_stat(density)` (use this syntax exactly - not changed for your data). Note: these proportions will show *per group*. Each is shown below (*note use of `color = ` vs. `fill = ` in each): ```{r, fig.show='hold', message=FALSE, warning=FALSE, out.width=c('50%', '50%')} # "Stacked" histogram -ggplot(data = linelist, mapping = aes(x = age, fill = gender)) + - geom_histogram(binwidth = 2)+ +ggplot(data = linelist, + mapping = aes(x = age, fill = gender)) + + geom_histogram(binwidth = 2) + labs(title = "'Stacked' histogram") # Frequency -ggplot(data = linelist, mapping = aes(x = age, color = gender)) + - geom_freqpoly(binwidth = 2, size = 2)+ +ggplot(data = linelist, + mapping = aes(x = age, color = gender)) + + geom_freqpoly(binwidth = 2, size = 2) + labs(title = "Freqpoly") # Frequency with proportion axis -ggplot(data = linelist, mapping = aes(x = age, y = after_stat(density), color = gender)) + - geom_freqpoly(binwidth = 5, size = 2)+ +ggplot(data = linelist, + mapping = aes(x = age, y = after_stat(density), color = gender)) + + geom_freqpoly(binwidth = 5, size = 2) + labs(title = "Proportional freqpoly") # Frequency with proportion axis, smoothed -ggplot(data = linelist, mapping = aes(x = age, y = after_stat(density), fill = gender)) + - geom_density(size = 2, alpha = 0.2)+ +ggplot(data = linelist, + mapping = aes(x = age, y = after_stat(density), fill = gender)) + + geom_density(size = 2, alpha = 0.2) + labs(title = "Proportional, smoothed with geom_density()") ``` -If you want to have some fun, try `geom_density_ridges` from the **ggridges** package ([vignette here](https://cran.r-project.org/web/packages/ggridges/vignettes/introduction.html). +If you want to have some fun, try `geom_density_ridges` from the **ggridges** package ([vignette here])(https://cran.r-project.org/web/packages/ggridges/vignettes/introduction.html). Read more in detail about histograms at the **tidyverse** [page on geom_histogram()](https://ggplot2.tidyverse.org/reference/geom_histogram.html). @@ -880,14 +898,14 @@ In most geoms, you create a plot per group by mapping an aesthetic like `color = ```{r fig.show='hold', message=FALSE, warning=FALSE, out.width=c('50%', '50%')} # A) Overall boxplot -ggplot(data = linelist)+ - geom_boxplot(mapping = aes(y = age))+ # only y axis mapped (not x) +ggplot(data = linelist) + + geom_boxplot(mapping = aes(y = age)) + # only y axis mapped (not x) labs(title = "A) Overall boxplot") # B) Box plot by group ggplot(data = linelist, mapping = aes(y = age, x = gender, fill = gender)) + - geom_boxplot()+ - theme(legend.position = "none")+ # remove legend (redundant) + geom_boxplot() + + theme(legend.position = "none") + # remove legend (redundant) labs(title = "B) Boxplot by gender") ``` @@ -908,8 +926,8 @@ Below is code for creating **violin plots** (`geom_violin`) and **jitter plots** ggplot(data = linelist %>% drop_na(outcome), # remove missing values mapping = aes(y = age, # Continuous variable x = outcome, # Grouping variable - color = outcome))+ # Color variable - geom_jitter()+ # Create the violin plot + color = outcome)) + # Color variable + geom_jitter() + # Create the violin plot labs(title = "A) jitter plot by gender") @@ -918,8 +936,8 @@ ggplot(data = linelist %>% drop_na(outcome), # remove missing values ggplot(data = linelist %>% drop_na(outcome), # remove missing values mapping = aes(y = age, # Continuous variable x = outcome, # Grouping variable - fill = outcome))+ # fill variable (color) - geom_violin()+ # create the violin plot + fill = outcome)) + # fill variable (color) + geom_violin() + # create the violin plot labs(title = "B) violin plot by gender") ``` @@ -931,15 +949,15 @@ You can combine the two using the `geom_sina()` function from the **ggforce** pa # A) Sina plot by group ggplot( data = linelist %>% drop_na(outcome), - aes(y = age, # numeric variable + mapping = aes(y = age, # numeric variable x = outcome)) + # group variable geom_violin( - aes(fill = outcome), # fill (color of violin background) + mapping = aes(fill = outcome), # fill (color of violin background) color = "white", # white outline - alpha = 0.2)+ # transparency + alpha = 0.2) + # transparency geom_sina( size=1, # Change the size of the jitter - aes(color = outcome))+ # color (color of dots) + mapping = aes(color = outcome)) + # color (color of dots) scale_fill_manual( # Define fill for violin background by death/recover values = c("Death" = "#bf5300", "Recover" = "#11118c")) + @@ -962,15 +980,15 @@ Following similar syntax, `geom_point()` will allow you to plot two continuous v ```{r fig.show='hold', message=FALSE, warning=FALSE, out.width=c('50%', '50%')} # Basic scatter plot of weight and age ggplot(data = linelist, - mapping = aes(y = wt_kg, x = age))+ + mapping = aes(y = wt_kg, x = age)) + geom_point() + labs(title = "A) Scatter plot of weight and age") # Scatter plot of weight and age by gender and Ebola outcome ggplot(data = linelist %>% drop_na(gender, outcome), # filter retains non-missing gender/outcome - mapping = aes(y = wt_kg, x = age))+ + mapping = aes(y = wt_kg, x = age)) + geom_point() + - labs(title = "B) Scatter plot of weight and age faceted by gender and outcome")+ + labs(title = "B) Scatter plot of weight and age faceted by gender and outcome") + facet_grid(gender ~ outcome) ``` @@ -978,7 +996,7 @@ ggplot(data = linelist %>% drop_na(gender, outcome), # filter retains non-missin ### Three continuous variables {.unnumbered} -You can display three continuous variables by utilizing the `fill = ` argument to create a *heat plot*. The color of each "cell" will reflect the value of the third continuous column of data. See the [ggplot tips](ggplot_tips.qmd) page and the page on on [Heat plots] for more details and several examples. +You can display three continuous variables by utilizing the `fill = ` argument to create a *heat plot*. The color of each "cell" will reflect the value of the third continuous column of data. See the [ggplot tips](ggplot_tips.qmd) page and the page on on [Heat plots](heatmaps.qmd) for more details and several examples. There are ways to make 3D plots in R, but for applied epidemiology these are often difficult to interpret and therefore less useful for decision-making. @@ -992,7 +1010,7 @@ There are ways to make 3D plots in R, but for applied epidemiology these are oft ## Plot categorical data -Categorical data can be character values, could be logical (TRUE/FALSE), or factors (see the [Factors] page). +Categorical data can be character values, could be logical (TRUE/FALSE), or factors (see the [Factors](factors.qmd) page). ### Preparation {.unnumbered} @@ -1000,8 +1018,8 @@ Categorical data can be character values, could be logical (TRUE/FALSE), or fact The first thing to understand about your categorical data is whether it exists as raw observations like a linelist of cases, or as a summary or aggregate data frame that holds counts or proportions. The state of your data will impact which plotting function you use: -* If your data are raw observations with one row per observation, you will likely use `geom_bar()` -* If your data are already aggregated into counts or proportions, you will likely use `geom_col()` +* If your data are raw observations with one row per observation, you will likely use `geom_bar()`. +* If your data are already aggregated into counts or proportions, you will likely use `geom_col()`. #### Column class and value ordering {.unnumbered} @@ -1042,25 +1060,25 @@ levels(linelist$hospital) Use `geom_bar()` if you want bar height (or the height of stacked bar components) to reflect *the number of relevant rows in the data*. These bars will have gaps between them, unless the `width = ` plot aesthetic is adjusted. -* Provide only one axis column assignment (typically x-axis). If you provide x and y, you will get `Error: stat_count() can only have an x or y aesthetic.` -* You can create stacked bars by adding a `fill = ` column assignment within `mapping = aes()` -* The opposite axis will be titled "count" by default, because it represents the number of rows +* Provide only one axis column assignment (typically x-axis). If you provide x and y, you will get `Error: stat_count() can only have an x or y aesthetic`. +* You can create stacked bars by adding a `fill = ` column assignment within `mapping = aes()`. +* The opposite axis will be titled "count" by default, because it represents the number of rows. Below, we have assigned outcome to the y-axis, but it could just as easily be on the x-axis. If you have longer character values, it can sometimes look better to flip the bars sideways and put the legend on the bottom. This may impact how your factor levels are ordered - in this case we reverse them with `fct_rev()` to put missing and other at the bottom. ```{r, out.width=c('50%', '50%'), fig.show='hold'} # A) Outcomes in all cases ggplot(linelist %>% drop_na(outcome)) + - geom_bar(aes(y = fct_rev(hospital)), width = 0.7) + - theme_minimal()+ + geom_bar(mapping = aes(y = fct_rev(hospital)), width = 0.7) + + theme_minimal() + labs(title = "A) Number of cases by hospital", y = "Hospital") # B) Outcomes in all cases by hosptial ggplot(linelist %>% drop_na(outcome)) + - geom_bar(aes(y = fct_rev(hospital), fill = outcome), width = 0.7) + - theme_minimal()+ + geom_bar(mapping = aes(y = fct_rev(hospital), fill = outcome), width = 0.7) + + theme_minimal() + theme(legend.position = "bottom") + labs(title = "B) Number of recovered and dead Ebola cases, by hospital", y = "Hospital") @@ -1097,7 +1115,7 @@ Below is code using `geom_col` for creating simple bar charts to show the distr ```{r, fig.height = 3, fig.width=4.5} # Outcomes in all cases ggplot(outcomes) + - geom_col(aes(x=outcome, y = proportion)) + + geom_col(mapping = aes(x=outcome, y = proportion)) + labs(subtitle = "Number of recovered and dead Ebola cases") ``` @@ -1128,16 +1146,16 @@ We then create the ggplot with some added formatting: ggplot(outcomes2) + geom_col( mapping = aes( - x = proportion, # show pre-calculated proportion values - y = fct_rev(hospital), # reverse level order so missing/other at bottom - fill = outcome), # stacked by outcome - width = 0.5)+ # thinner bars (out of 1) + x = proportion, # show pre-calculated proportion values + y = fct_rev(hospital), # reverse level order so missing/other at bottom + fill = outcome), # stacked by outcome + width = 0.5) + # thinner bars (out of 1) theme_minimal() + # Minimal theme - theme(legend.position = "bottom")+ - labs(subtitle = "Number of recovered and dead Ebola cases, by hospital", + theme(legend.position = "bottom") + + labs(subtitle = "Proportio of recovered and dead Ebola cases, by hospital", fill = "Outcome", # legend title - y = "Count", # y axis title - x = "Hospital of admission")+ # x axis title + x = "Proportion", # y axis title + y = "Hospital of admission") + # x axis title scale_fill_manual( # adding colors manually values = c("Death"= "#3B1c8C", "Recover" = "#21908D" )) diff --git a/new_pages/ggplot_tips.qmd b/new_pages/ggplot_tips.qmd index c2d73631..18409ab8 100644 --- a/new_pages/ggplot_tips.qmd +++ b/new_pages/ggplot_tips.qmd @@ -21,6 +21,7 @@ pacman::p_load( here, # file locator stringr, # working with characters scales, # transform numbers + cowplot, # for dual axes ggrepel, # smartly-placed labels gghighlight, # highlight one part of plot RColorBrewer # color scales @@ -31,7 +32,7 @@ pacman::p_load( For this page, we import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -59,7 +60,7 @@ In **ggplot2**, when aesthetics of plotted data (e.g. size, color, shape, fill, ### Color schemes -One thing that can initially be difficult to understand with **ggplot2** is control of color schemes. Note that this section discusses the color of *plot objects* (geoms/shapes) such as points, bars, lines, tiles, etc. To adjust color of accessory text, titles, or background color see the [Themes](ggplot_basics.qmd#ggplot_basics_themes) section of the [ggplot basics] page. +One thing that can initially be difficult to understand with **ggplot2** is control of color schemes. Note that this section discusses the color of *plot objects* (geoms/shapes) such as points, bars, lines, tiles, etc. To adjust color of accessory text, titles, or background color see the [Themes](ggplot_basics.qmd#ggplot_basics_themes) section of the [ggplot basics](ggplot_basics.qmd) page. To control "color" of *plot objects* you will be adjusting either the `color = ` aesthetic (the *exterior* color) or the `fill = ` aesthetic (the *interior* color). One exception to this pattern is `geom_point()`, where you really only get to control `color = `, which controls the color of the point (interior and exterior). @@ -67,7 +68,8 @@ When setting colour or fill you can use colour names recognized by R like `"red" ```{r, warning=F, message=F} # histogram - -ggplot(data = linelist, mapping = aes(x = age))+ # set data and axes +ggplot(data = linelist, + mapping = aes(x = age)) + # set data and axes geom_histogram( # display histogram binwidth = 7, # width of bins color = "red", # bin line color @@ -76,28 +78,32 @@ ggplot(data = linelist, mapping = aes(x = age))+ # set data and axes -As explained the [ggplot basics](ggplot_basics.qmd) section on [mapping data to the plot](ggplot_basics.qmd#ggplot_basics_mapping), aesthetics such as `fill = ` and `color = ` can be defined either *outside* of a `mapping = aes()` statement or *inside* of one. If *outside* the `aes()`, the assigned value should be static (e.g. `color = "blue"`) and will apply for *all* data plotted by the geom. If *inside*, the aesthetic should be mapped to a column, like `color = hospital`, and the expression will vary by the value for that row in the data. A few examples: +As explained the [ggplot basics](ggplot_basics.qmd) section on [mapping data to the plot](ggplot_basics.qmd#ggplot_basics_mapping), aesthetics such as `fill = ` and `color = ` can be defined either *outside* of a `mapping = aes()` statement, or *inside* of one. If *outside* the `aes()`, the assigned value should be static (e.g. `color = "blue"`) and will apply for *all* data plotted by the geom. If *inside*, the aesthetic should be mapped to a column, like `color = hospital`, and the expression will vary by the value for that row in the data. A few examples: ```{r, out.width=c('50%', '50%'), fig.show='hold', warning=F, message=F} # Static color for points and for line -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(color = "purple")+ - geom_vline(xintercept = 50, color = "orange")+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(color = "purple") + + geom_vline(xintercept = 50, color = "orange") + labs(title = "Static color for points and line") # Color mapped to continuous column -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(mapping = aes(color = temp))+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(mapping = aes(color = temp)) + labs(title = "Color mapped to continuous column") # Color mapped to discrete column -ggplot(data = linelist, mapping = aes(x = age, y = wt_kg))+ - geom_point(mapping = aes(color = gender))+ +ggplot(data = linelist, + mapping = aes(x = age, y = wt_kg)) + + geom_point(mapping = aes(color = gender)) + labs(title = "Color mapped to discrete column") # bar plot, fill to discrete column, color to static value -ggplot(data = linelist, mapping = aes(x = hospital))+ - geom_bar(mapping = aes(fill = gender), color = "yellow")+ +ggplot(data = linelist, + mapping = aes(y = hospital)) + + geom_bar(mapping = aes(fill = gender), color = "yellow") + labs(title = "Fill mapped to discrete column, static color") ``` @@ -110,8 +116,8 @@ Once you map a column to a plot aesthetic (e.g. `x = `, `y = `, `fill = `, `colo You can control the scales with the appropriate `scales_()` function. The scale functions of **ggplot()** have 3 parts that are written like this: `scale_AESTHETIC_METHOD()`. 1) The first part, `scale_()`, is fixed. -2) The second part, the AESTHETIC, should be the aesthetic that you want to adjust the scale for (`_fill_`, `_shape_`, `_color_`, `_size_`, `_alpha_`...) - the options here also include `_x_` and `_y_`. -3) The third part, the METHOD, will be either `_discrete()`, `continuous()`, `_date()`, `_gradient()`, or `_manual()` depending on the class of the column and *how* you want to control it. There are others, but these are the most-often used. +2) The second part, the AESTHETIC, should be the aesthetic that you want to adjust the scale for (`_fill_`, `_shape_`, `_color_`, `_size_`, `_alpha_`...) - the options here also include `_x_` and `_y_` +3) The third part, the METHOD, will be either `_discrete()`, `continuous()`, `_date()`, `_gradient()`, or `_manual()` depending on the class of the column and *how* you want to control it. There are others, but these are the most-often used Be sure that you use the correct function for the scale! Otherwise your scale command will not appear to change anything. If you have multiple scales, you may use multiple scale functions to adjust them! For example: @@ -119,7 +125,7 @@ Be sure that you use the correct function for the scale! Otherwise your scale co Each kind of scale has its own arguments, though there is some overlap. Query the function like `?scale_color_discrete` in the R console to see the function argument documentation. -For continuous scales, use `breaks = ` to provide a sequence of values with `seq()` (take `to = `, `from = `, and `by = ` as shown in the example below. Set `expand = c(0,0)` to eliminate padding space around the axes (this can be used on any `_x_` or `_y_` scale. +For continuous scales, use `breaks = ` to provide a sequence of values with `seq()` (take `to = `, `from = `, and `by = ` as shown in the example below. Set `expand = c(0,0)` to eliminate padding space around the axes (this can be used on any `_x_` or `_y_` scale). For discrete scales, you can adjust the order of level appearance with `breaks = `, and how the values display with the `labels = ` argument. Provide a character vector to each of those (see example below). You can also drop `NA` easily by setting `na.translate = FALSE`. @@ -130,10 +136,10 @@ The nuances of date scales are covered more extensively in the [Epidemic curves] One of the most useful tricks is using "manual" scaling functions to explicitly assign colors as you desire. These are functions with the syntax `scale_xxx_manual()` (e.g. `scale_colour_manual()` or `scale_fill_manual()`). Each of the below arguments are demonstrated in the code example below. -* Assign colors to data values with the `values = ` argument -* Specify a color for `NA` with `na.value = ` -* Change how the values are *written* in the legend with the `labels = ` argument -* Change the legend title with `name = ` +* Assign colors to data values with the `values = ` argument +* Specify a color for `NA` with `na.value = ` +* Change how the values are *written* in the legend with the `labels = ` argument +* Change the legend title with `name = ` Below, we create a bar plot and show how it appears by default, and then with three scales adjusted - the continuous y-axis scale, the discrete x-axis scale, and manual adjustment of the fill (interior bar color). @@ -141,28 +147,28 @@ Below, we create a bar plot and show how it appears by default, and then with th ```{r, warning=F, message=F} # BASELINE - no scale adjustment -ggplot(data = linelist)+ - geom_bar(mapping = aes(x = outcome, fill = gender))+ +ggplot(data = linelist) + + geom_bar(mapping = aes(x = outcome, fill = gender)) + labs(title = "Baseline - no scale adjustments") # SCALES ADJUSTED -ggplot(data = linelist)+ +ggplot(data = linelist) + - geom_bar(mapping = aes(x = outcome, fill = gender), color = "black")+ + geom_bar(mapping = aes(x = outcome, fill = gender), color = "black") + - theme_minimal()+ # simplify background + theme_minimal() + # simplify background scale_y_continuous( # continuous scale for y-axis (counts) expand = c(0,0), # no padding breaks = seq(from = 0, to = 3000, - by = 500))+ + by = 500)) + scale_x_discrete( # discrete scale for x-axis (gender) expand = c(0,0), # no padding drop = FALSE, # show all factor levels (even if not in data) na.translate = FALSE, # remove NA outcomes from plot - labels = c("Died", "Recovered"))+ # Change display of values + labels = c("Died", "Recovered")) + # Change display of values scale_fill_manual( # Manually specify fill (bar interior color) @@ -173,7 +179,7 @@ ggplot(data = linelist)+ "Missing"), name = "Gender", # title of legend na.value = "grey" # assign a color for missing values - )+ + ) + labs(title = "Adjustment of scales") # Adjust the title of the fill legend ``` @@ -185,19 +191,19 @@ We may want to adjust the breaks or display of the values in the ggplot using `s ```{r, warning=F, message=F, out.width=c('50%', '50%'), fig.show='hold'} # BASELINE - no scale adjustment -ggplot(data = linelist)+ - geom_bar(mapping = aes(x = outcome, fill = gender))+ +ggplot(data = linelist) + + geom_bar(mapping = aes(x = outcome, fill = gender)) + labs(title = "Baseline - no scale adjustments") -# -ggplot(data = linelist)+ - geom_bar(mapping = aes(x = outcome, fill = gender))+ +# Updated - scale adjustment +ggplot(data = linelist) + + geom_bar(mapping = aes(x = outcome, fill = gender)) + scale_y_continuous( breaks = seq( from = 0, to = 3000, by = 100) - )+ + ) + labs(title = "Adjusted y-axis breaks") ``` @@ -223,9 +229,9 @@ linelist %>% # start with linelist ggplot( # begin plotting mapping = aes( x = hospital, - y = prop_death))+ - geom_col()+ - theme_minimal()+ + y = prop_death)) + + geom_col() + + theme_minimal() + labs(title = "Display y-axis original proportions") @@ -241,10 +247,10 @@ linelist %>% ggplot( mapping = aes( x = hospital, - y = prop_death))+ - geom_col()+ - theme_minimal()+ - labs(title = "Display y-axis as percents (%)")+ + y = prop_death)) + + geom_col() + + theme_minimal() + + labs(title = "Display y-axis as percents (%)") + scale_y_continuous( labels = scales::percent # display proportions as percents ) @@ -272,11 +278,11 @@ The cumulative cases for region "I" are dramatically greater than all the other preparedness_plot <- ggplot(data = plot_data, mapping = aes( x = preparedness_index, - y = cases_cumulative))+ - geom_point(size = 2)+ # points for each region + y = cases_cumulative)) + + geom_point(size = 2) + # points for each region geom_text( mapping = aes(label = region), - vjust = 1.5)+ # add text labels + vjust = 1.5) + # add text labels theme_minimal() preparedness_plot # print original plot @@ -306,11 +312,11 @@ Below, we produce a "raster" heat tile density plot. We won't elaborate how (see ```{r, warn=F, message=F} trans_matrix <- ggplot( data = case_source_relationships, - mapping = aes(x = source_age, y = target_age))+ + mapping = aes(x = source_age, y = target_age)) + stat_density2d( geom = "raster", mapping = aes(fill = after_stat(density)), - contour = FALSE)+ + contour = FALSE) + theme_minimal() ``` @@ -323,9 +329,9 @@ trans_matrix + scale_fill_viridis_c(option = "plasma") Now we show some examples of actually adjusting the break points of the scale: -* `scale_fill_gradient()` accepts two colors (high/low) -* `scale_fill_gradientn()` accepts a vector of any length of colors to `values = ` (intermediate values will be interpolated) -* Use [`scales::rescale()`](https://www.rdocumentation.org/packages/scales/versions/0.4.1/topics/rescale) to adjust how colors are positioned along the gradient; it rescales your vector of positions to be between 0 and 1. +* `scale_fill_gradient()` accepts two colors (high/low) +* `scale_fill_gradientn()` accepts a vector of any length of colors to `values = ` (intermediate values will be interpolated) +* Use [`scales::rescale()`](https://www.rdocumentation.org/packages/scales/versions/0.4.1/topics/rescale) to adjust how colors are positioned along the gradient; it rescales your vector of positions to be between 0 and 1 ```{r, out.width=c('50%', '50%'), fig.show='hold', warning=F, message=F} @@ -334,14 +340,14 @@ trans_matrix + low = "aquamarine", # low value high = "purple", # high value na.value = "grey", # value for NA - name = "Density")+ # Legend title + name = "Density") + # Legend title labs(title = "Manually specify high/low colors") # 3+ colors to scale trans_matrix + scale_fill_gradientn( # 3-color scale (low/mid/high) colors = c("blue", "yellow","red") # provide colors in vector - )+ + ) + labs(title = "3-color scale") # Use of rescale() to adjust placement of colors along scale @@ -349,14 +355,14 @@ trans_matrix + scale_fill_gradientn( # provide any number of colors colors = c("blue", "yellow","red", "black"), values = scales::rescale(c(0, 0.05, 0.07, 0.10, 0.15, 0.20, 0.3, 0.5)) # positions for colors are rescaled between 0 and 1 - )+ + ) + labs(title = "Colors not evenly positioned") # use of limits to cut-off values that get fill color trans_matrix + scale_fill_gradientn( colors = c("blue", "yellow","red"), - limits = c(0, 0.0002))+ + limits = c(0, 0.0002)) + labs(title = "Restrict value limits, resulting in grey space") ``` @@ -367,7 +373,7 @@ trans_matrix + #### Colorbrewer and Viridis {.unnumbered} More generally, if you want predefined palettes, you can use the `scale_xxx_brewer` or `scale_xxx_viridis_y` functions. -The 'brewer' functions can draw from [colorbrewer.org](colorbrewer.org) palettes. +The 'brewer' functions can draw from [colorbrewer2.org](colorbrewer2.org) palettes. The 'viridis' functions draw from viridis (colourblind friendly!) palettes, which "provide colour maps that are perceptually uniform in both colour and black-and-white. They are also designed to be perceived by viewers with common forms of colour blindness." (read more [here](https://ggplot2.tidyverse.org/reference/scale_viridis.html) and [here](https://bids.github.io/colormap/)). Define if the palette is discrete, continuous, or binned by specifying this at the end of the function (e.g. discrete is `scale_xxx_viridis_d`). @@ -385,10 +391,10 @@ symp_plot <- linelist %>% # begin with l mutate( # replace missing values symptom_is_present = replace_na(symptom_is_present, "unknown")) %>% ggplot( # begin ggplot! - mapping = aes(x = symptom_name, fill = symptom_is_present))+ + mapping = aes(x = symptom_name, fill = symptom_is_present)) + geom_bar(position = "fill", col = "black") + theme_classic() + - theme(legend.position = "bottom")+ + theme(legend.position = "bottom") + labs( x = "Symptom", y = "Symptom status (proportion)" @@ -444,11 +450,15 @@ ggplot( ``` -#### **ggthemr** {.unnnumbered} +#### **Other color palette packages** {.unnnumbered} -Also consider using the **ggthemr** package. You can download this package from Github using the instructions [here](https://github.com/Mikata-Project/ggthemr). It offers palettes that are very aesthetically pleasing, but be aware that these typically have a maximum number of values that can be limiting if you want more than 7 or 8 colors. +One great thing about R is the wealth of packages out there. This is no different for color palettes. For example: +* The [**ggsci package**](https://cran.r-project.org/web/packages/ggsci/vignettes/ggsci.html), which contains a wealth of scientific journal, and scifi tv show, color schemes +* The [**wesanderson package**](https://github.com/karthik/wesanderson) which lists a series of color schemes around the films of Wes Anderson +* The **ggthemr** package. You can download this package from Github using the instructions [here](https://github.com/Mikata-Project/ggthemr). It offers palettes that are very aesthetically pleasing, but be aware that these typically have a maximum number of values that can be limiting if you want more than 7 or 8 colors +These are just a few examples, there are many more out there for you to find and try out! @@ -460,17 +470,17 @@ Contour plots are helpful when you have many points that might cover each other ```{r, out.width=c('50%'), fig.show='hold', warning=F, message=F} case_source_relationships %>% - ggplot(aes(x = source_age, y = target_age))+ - stat_density2d()+ - geom_point()+ - theme_minimal()+ + ggplot(mapping = aes(x = source_age, y = target_age)) + + stat_density2d() + + geom_point() + + theme_minimal() + labs(title = "stat_density2d() + geom_point()") case_source_relationships %>% - ggplot(aes(x = source_age, y = target_age))+ - stat_density2d_filled()+ - theme_minimal()+ + ggplot(mapping = aes(x = source_age, y = target_age)) + + stat_density2d_filled() + + theme_minimal() + labs(title = "stat_density2d_filled()") ``` @@ -481,10 +491,10 @@ case_source_relationships %>% To show the distributions on the edges of a `geom_point()` scatterplot, you can use the **ggExtra** package and its function `ggMarginal()`. Save your original ggplot as an object, then pass it to `ggMarginal()` as shown below. Here are the key arguments: -* You must specify the `type = ` as either "histogram", "density" "boxplot", "violin", or "densigram". -* By default, marginal plots will appear for both axes. You can set `margins = ` to "x" or "y" if you only want one. -* Other optional arguments include `fill = ` (bar color), `color = ` (line color), `size = ` (plot size relative to margin size, so larger number makes the marginal plot smaller). -* You can provide other axis-specific arguments to `xparams = ` and `yparams = `. For example, to have different histogram bin sizes, as shown below. +* You must specify the `type = ` as either "histogram", "density" "boxplot", "violin", or "densigram" +* By default, marginal plots will appear for both axes. You can set `margins = ` to "x" or "y" if you only want one +* Other optional arguments include `fill = ` (bar color), `color = ` (line color), `size = ` (plot size relative to margin size, so larger number makes the marginal plot smaller) +* You can provide other axis-specific arguments to `xparams = ` and `yparams = `. For example, to have different histogram bin sizes, as shown below You can have the marginal plots reflect groups (columns that have been assigned to `color = ` in your `ggplot()` mapped aesthetics). If this is the case, set the `ggMarginal()` argument `groupColour = ` or `groupFill = ` to `TRUE`, as shown below. @@ -495,7 +505,7 @@ Read more at [this vignette](https://cran.r-project.org/web/packages/ggExtra/vig pacman::p_load(ggExtra) # Basic scatter plot of weight and age -scatter_plot <- ggplot(data = linelist)+ +scatter_plot <- ggplot(data = linelist) + geom_point(mapping = aes(y = wt_kg, x = age)) + labs(title = "Scatter plot of weight and age") ``` @@ -518,9 +528,9 @@ Marginal density plot with grouped/colored values: # Scatter plot, colored by outcome # Outcome column is assigned as color in ggplot. groupFill in ggMarginal set to TRUE -scatter_plot_color <- ggplot(data = linelist %>% drop_na(gender))+ +scatter_plot_color <- ggplot(data = linelist %>% drop_na(gender)) + geom_point(mapping = aes(y = wt_kg, x = age, color = gender)) + - labs(title = "Scatter plot of weight and age")+ + labs(title = "Scatter plot of weight and age") + theme(legend.position = "bottom") ggMarginal(scatter_plot_color, type = "density", groupFill = TRUE) @@ -547,11 +557,11 @@ The **ggrepel** package provides two new functions, `geom_label_repel()` and `ge A few tips: -* Use `min.segment.length = 0` to always draw line segments, or `min.segment.length = Inf` to never draw them -* Use `size = ` outside of `aes()` to set text size +* Use `min.segment.length = 0` to always draw line segments, or `min.segment.length = Inf` to never draw them +* Use `size = ` outside of `aes()` to set text size * Use `force = ` to change the degree of repulsion between labels and their respective points (default is 1) -* Include `fill = ` within `aes()` to have label colored by value - * A letter "a" may appear in the legend - add `guides(fill = guide_legend(override.aes = aes(color = NA)))+` to remove it +* Include `fill = ` within `aes()` to have label colored by value + * A letter "a" may appear in the legend - add `guides(fill = guide_legend(override.aes = aes(color = NA))) +` to remove it See this is very in-depth [tutorial](https://ggrepel.slowkow.com/articles/examples.html) for more. @@ -564,15 +574,15 @@ linelist %>% # start with linelist n_cases = n(), # number of cases per hospital delay_mean = round(mean(days_onset_hosp, na.rm=T),1), # mean delay per hospital ) %>% - ggplot(mapping = aes(x = n_cases, y = delay_mean))+ # send data frame to ggplot - geom_point(size = 2)+ # add points + ggplot(mapping = aes(x = n_cases, y = delay_mean)) + # send data frame to ggplot + geom_point(size = 2) + # add points geom_label_repel( # add point labels mapping = aes( label = stringr::str_glue( "{hospital}\n{n_cases} cases, {delay_mean} days") # how label displays ), size = 3, # text size in labels - min.segment.length = 0)+ # show all line segments + min.segment.length = 0) + # show all line segments labs( # add axes labels title = "Mean delay to admission, by hospital", x = "Number of cases", @@ -583,19 +593,19 @@ You can label only a subset of the data points - by using standard `ggplot()` sy ```{r, warning=F, message=FALSE} -ggplot()+ +ggplot() + # All points in grey geom_point( data = linelist, # all data provided to this layer mapping = aes(x = ht_cm, y = wt_kg), color = "grey", - alpha = 0.5)+ # grey and semi-transparent + alpha = 0.5) + # grey and semi-transparent # Few points in black geom_point( data = linelist %>% filter(days_onset_hosp > 15), # filtered data provided to this layer mapping = aes(x = ht_cm, y = wt_kg), - alpha = 1)+ # default black and not transparent + alpha = 1) + # default black and not transparent # point labels for few points geom_label_repel( @@ -608,7 +618,7 @@ ggplot()+ min.segment.length = 0) + # show line segments for all # remove letter "a" from inside legend boxes - guides(fill = guide_legend(override.aes = aes(color = NA)))+ + guides(fill = guide_legend(override.aes = aes(color = NA))) + # axis labels labs( @@ -630,12 +640,13 @@ The single most useful set of functions for working with dates in `ggplot2` are 1. `date_breaks` allows you to specify how often axis breaks occur - you can pass a string here (e.g. `"3 months"`, or "`2 days"`) - 2. `date_labels` allows you to define the format dates are shown in. You can pass a date format string to these arguments (e.g. `"%b-%d-%Y"`): + 2. `date_labels` allows you to define the format dates are shown in. You can pass a date format string to these arguments (e.g. `"%b-%d-%Y"`) ```{r, , warning=F, message=F} # make epi curve by date of onset when available -ggplot(linelist, aes(x = date_onset)) + +ggplot(linelist, + mapping = aes(x = date_onset)) + geom_histogram(binwidth = 7) + scale_x_date( # 1 break every 1 month @@ -650,14 +661,14 @@ ggplot(linelist, aes(x = date_onset)) + One easy solution to efficient date labels on the x-axis is to to assign the `labels = ` argument in `scale_x_date()` to the function `label_date_short()` from the package **scales**. This function will automatically construct efficient date labels (read more [here](https://scales.r-lib.org/reference/label_date.html)). An additional benefit of this function is that the labels will automatically adjust as your data expands over time, from days, to weeks, to months and years. -See a complete example in the Epicurves page section on [multi-level date labels](https://epirhandbook.com/en/epidemic-curves.html#multi-level-date-labels), but a quick example is shown below for reference: +See a complete example in the Epicurves page section on [multi-level date labels](epicurves.qmd#multi-level-date-labels), but a quick example is shown below for reference: ```{r, eval=T, warning=F} ggplot(linelist, aes(x = date_onset)) + geom_histogram(binwidth = 7) + scale_x_date( labels = scales::label_date_short() # automatically efficient date labels - )+ + ) + theme_classic() ``` @@ -671,7 +682,7 @@ The **gghighlight** package uses the `gghighlight()` function to achieve this ef ```{r, , warning=F, message=F} # load gghighlight -library(gghighlight) +pacman::p_load(gghighlight) # replace NA values with unknown in the outcome variable linelist <- linelist %>% @@ -690,15 +701,18 @@ This also works well with faceting functions - it allows the user to produce fac ```{r, , warning=F, message=F} -# produce a histogram of all cases by age +# produce a linegraph of all cases by age linelist %>% count(week = lubridate::floor_date(date_hospitalisation, "week"), hospital) %>% - ggplot()+ - geom_line(aes(x = week, y = n, color = hospital))+ - theme_minimal()+ + ggplot() + + geom_line(mapping = aes(x = week, + y = n, + color = hospital)) + + theme_minimal() + gghighlight::gghighlight() + # highlight instances where the patient has died - facet_wrap(~hospital) # make facets by outcome + facet_wrap(~hospital) + # make facets by outcome + scale_x_date(labels = date_format("%m/%y")) ``` @@ -710,8 +724,8 @@ linelist %>% Note that properly aligning axes to plot from multiple datasets in the same plot can be difficult. Consider one of the following strategies: -* Merge the data prior to plotting, and convert to "long" format with a column reflecting the dataset -* Use **cowplot** or a similar package to combine two plots (see below) +* Merge the data prior to plotting, and convert to "long" format with a column reflecting the dataset. +* Use **patchwork** or a similar package to combine two plots (see below). @@ -721,33 +735,37 @@ Note that properly aligning axes to plot from multiple datasets in the same plot ## Combine plots {} -Two packages that are very useful for combining plots are **cowplot** and **patchwork**. In this page we will mostly focus on **cowplot**, with occassional use of **patchwork**. +Two packages that are very useful for combining plots are **cowplot** and **patchwork**. In this page we will mostly focus on **patchwork**. + +Here is the online [introduction to cowplot](https://cran.r-project.org/web/packages/cowplot/vignettes/introduction.html). You can read the more extensive documentation for each function online [here](https://www.rdocumentation.org/packages/cowplot/versions/1.1.1). -Here is the online [introduction to cowplot](https://cran.r-project.org/web/packages/cowplot/vignettes/introduction.html). You can read the more extensive documentation for each function online [here](https://www.rdocumentation.org/packages/cowplot/versions/1.1.1). We will cover a few of the most common use cases and functions below. +**patchwork** allows us to combine separate ggplots into the same graphic using a very easy syntax to add, change layouts, and heavily customise our plots. -The **cowplot** package works in tandem with **ggplot2** - essentially, you use it to arrange and combine ggplots and their legends into compound figures. It can also accept **base** R graphics. ```{r} pacman::p_load( tidyverse, # data manipulation and visualisation - cowplot, # combine plots patchwork # combine plots ) ``` -While faceting (described in the [ggplot basics](ggplot_basics.qmd) page) is a convenient approach to plotting, sometimes its not possible to get the results you want from its relatively restrictive approach. Here, you may choose to combine plots by sticking them together into a larger plot. There are three well known packages that are great for this - **cowplot**, **gridExtra**, and **patchwork**. However, these packages largely do the same things, so we'll focus on **cowplot** for this section. +While faceting (described in the [ggplot basics](ggplot_basics.qmd) page) is a convenient approach to plotting, sometimes its not possible to get the results you want from its relatively restrictive approach. Here, you may choose to combine plots by sticking them together into a larger plot. There are three well known packages that are great for this - **cowplot**, **gridExtra**, and **patchwork**. However, these packages largely do the same things, so we'll focus on **patchwork** for this section. ### `plot_grid()` {.unnumbered} -The **cowplot** package has a fairly wide range of functions, but the easiest use of it can be achieved through the use of `plot_grid()`. This is effectively a way to arrange predefined plots in a grid formation. We can work through another example with the malaria dataset - here we can plot the total cases by district, and also show the epidemic curve over time. +The **patchwork** package has a very simple syntax for adding `ggplot()` objects together. You simply use `+`. + +This is effectively a way to arrange predefined plots in a grid formation. We can work through another example with the malaria dataset - here we can plot the total cases by district, and also show the epidemic curve over time. ```{r, , warning=F, message=F} malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) # bar chart of total cases by district -p1 <- ggplot(malaria_data, aes(x = District, y = malaria_tot)) + +p1 <- ggplot(malaria_data, + mapping = aes(x = District, + y = malaria_tot)) + geom_bar(stat = "identity") + labs( x = "District", @@ -757,7 +775,9 @@ p1 <- ggplot(malaria_data, aes(x = District, y = malaria_tot)) + theme_minimal() # epidemic curve over time -p2 <- ggplot(malaria_data, aes(x = data_date, y = malaria_tot)) + +p2 <- ggplot(malaria_data, + mapping = aes(x = data_date, + y = malaria_tot)) + geom_col(width = 1) + labs( x = "Date of data submission", @@ -765,146 +785,91 @@ p2 <- ggplot(malaria_data, aes(x = data_date, y = malaria_tot)) + ) + theme_minimal() -cowplot::plot_grid(p1, p2, - # 1 column and two rows - stacked on top of each other - ncol = 1, - nrow = 2, - # top plot is 2/3 as tall as second - rel_heights = c(2, 3)) +p1 + p2 ``` +And if we wanted to put `p1` above `p2` we would use `/` +```{r, warning=F, message=F} +p1 / p2 +``` +There is also a large amount of flexibility in customising how the plots are arranged and sized. To do this we use the function `plot_layout()`. +This allows us to customise things like the number of columns and rows we want our plots arranged on, and the widths and heights. -### Combine legends {.unnumbered} - -If your plots have the same legend, combining them is relatively straight-forward. Simple use the **cowplot** approach above to combine the plots, but remove the legend from one of them (de-duplicate). - -If your plots have different legends, you must use an alternative approach: - -1) Create and save your plots *without legends* using `theme(legend.position = "none")` -2) Extract the legends from each plot using `get_legend()` as shown below - *but extract legends from the plots modified to actually show the legend* -3) Combine the legends into a legends panel -4) Combine the plots and legends panel - - -For demonstration we show the two plots separately, and then arranged in a grid with their own legends showing (ugly and inefficient use of space): +For instance if we wanted to put two plots on-top of each other, and make the bottom one smaller, we could this. -```{r, out.width=c('50%'), fig.show='hold', warning=F, message=F} -p1 <- linelist %>% - mutate(hospital = recode(hospital, "St. Mark's Maternity Hospital (SMMH)" = "St. Marks")) %>% - count(hospital, outcome) %>% - ggplot()+ - geom_col(mapping = aes(x = hospital, y = n, fill = outcome))+ - scale_fill_brewer(type = "qual", palette = 4, na.value = "grey")+ - coord_flip()+ - theme_minimal()+ - labs(title = "Cases by outcome") +```{r} - -p2 <- linelist %>% - mutate(hospital = recode(hospital, "St. Mark's Maternity Hospital (SMMH)" = "St. Marks")) %>% - count(hospital, age_cat) %>% - ggplot()+ - geom_col(mapping = aes(x = hospital, y = n, fill = age_cat))+ - scale_fill_brewer(type = "qual", palette = 1, na.value = "grey")+ - coord_flip()+ - theme_minimal()+ - theme(axis.text.y = element_blank())+ - labs(title = "Cases by age") +p1 + p2 + + plot_layout(heights = c(3, 1)) ``` -Here is how the two plots look when combined using `plot_grid()` without combining their legends: -```{r, warning=F, message=F} -cowplot::plot_grid(p1, p2, rel_widths = c(0.3)) -``` +### Combine legends {.unnumbered} -And now we show how to combine the legends. Essentially what we do is to define each plot *without* its legend (`theme(legend.position = "none"`), and then we define each plot's legend *separately*, using the `get_legend()` function from **cowplot**. When we extract the legend from the saved plot, we need to add `+` the legend back in, including specifying the placement ("right") and smaller adjustments for alignment of the legends and their titles. Then, we combine the legends together vertically, and then combine the two plots with the newly-combined legends. Voila! +If your plots have the same legend, with the same scale, combining them is relatively straight-forward. Simple use the **patchwork** approach above to combine the plots, but remove the legend from one of them (de-duplicate by setting `theme(legend.position = "none"`)). -```{r, warning=F, message=F} +If you have two separate legends, by default the legend will be placed to the right of each plot. -# Define plot 1 without legend +```{r, out.width=c('50%'), fig.show='hold', warning=F, message=F} p1 <- linelist %>% mutate(hospital = recode(hospital, "St. Mark's Maternity Hospital (SMMH)" = "St. Marks")) %>% count(hospital, outcome) %>% - ggplot()+ - geom_col(mapping = aes(x = hospital, y = n, fill = outcome))+ - scale_fill_brewer(type = "qual", palette = 4, na.value = "grey")+ - coord_flip()+ - theme_minimal()+ - theme(legend.position = "none")+ + ggplot() + + geom_col(mapping = aes(x = hospital, y = n, fill = outcome)) + + scale_fill_brewer(type = "qual", palette = 4, na.value = "grey") + + coord_flip() + + theme_minimal() + labs(title = "Cases by outcome") -# Define plot 2 without legend p2 <- linelist %>% mutate(hospital = recode(hospital, "St. Mark's Maternity Hospital (SMMH)" = "St. Marks")) %>% count(hospital, age_cat) %>% - ggplot()+ - geom_col(mapping = aes(x = hospital, y = n, fill = age_cat))+ - scale_fill_brewer(type = "qual", palette = 1, na.value = "grey")+ - coord_flip()+ - theme_minimal()+ - theme( - legend.position = "none", - axis.text.y = element_blank(), - axis.title.y = element_blank() - )+ + ggplot() + + geom_col(mapping = aes(x = hospital, y = n, fill = age_cat)) + + scale_fill_brewer(type = "qual", palette = 1, na.value = "grey") + + coord_flip() + + theme_minimal() + + theme(axis.text.y = element_blank()) + labs(title = "Cases by age") +p1 + p2 -# extract legend from p1 (from p1 + legend) -leg_p1 <- cowplot::get_legend(p1 + - theme(legend.position = "right", # extract vertical legend - legend.justification = c(0,0.5))+ # so legends align - labs(fill = "Outcome")) # title of legend -# extract legend from p2 (from p2 + legend) -leg_p2 <- cowplot::get_legend(p2 + - theme(legend.position = "right", # extract vertical legend - legend.justification = c(0,0.5))+ # so legends align - labs(fill = "Age Category")) # title of legend - -# create a blank plot for legend alignment -#blank_p <- patchwork::plot_spacer() + theme_void() - -# create legends panel, can be one on top of the other (or use spacer commented above) -legends <- cowplot::plot_grid(leg_p1, leg_p2, nrow = 2, rel_heights = c(.3, .7)) +``` -# combine two plots and the combined legends panel -combined <- cowplot::plot_grid(p1, p2, legends, ncol = 3, rel_widths = c(.4, .4, .2)) +This can be an inefficient use of space. To place the two plots together on the same side, you can use the `plot_layout()` function. This function can control a number of different ways the plots are laid out, but here we will use it to place the legends on the same side. -combined # print +```{r, warning=F, message=F} +p1 + p2 + plot_layout(guides = "collect") ``` -This solution was learned from [this post](https://stackoverflow.com/questions/52060601/ggplot-multiple-legends-arrangement) with a minor fix to align legends from [this post](https://github.com/wilkelab/cowplot/issues/33). - - -**_TIP:_** Fun note - the "cow" in **cowplot** comes from the creator's name - Claus O. Wilke. - +A much better use of space! ### Inset plots {.unnumbered} -You can inset one plot in another using **cowplot**. Here are things to be aware of: - -* Define the main plot with `theme_half_open()` from **cowplot**; it may be best to have the legend either on top or bottom -* Define the inset plot. Best is to have a plot where you do not need a legend. You can remove plot theme elements with `element_blank()` as shown below. -* Combine them by applying `ggdraw()` to the main plot, then adding `draw_plot()` on the inset plot and specifying the coordinates (x and y of lower left corner), height and width as proportion of the whole main plot. +You can inset one plot in another using **patchwork**. This is done by using the function `inset_element()` which takes a few main arguments. +* p = the plot you want to insert +* left = the proportion along the plot you want the left side of the plot to be +* bottom = the proportion along the plot you want the bottom side of the plot to be +* right = the proportion along the plot you want the right side of the plot to be +* top = the proportion along the plot you want the right side of the plot to be ```{r, out.width=c('100%'), fig.show='hold', warning=F, message=F} # Define main plot -main_plot <- ggplot(data = linelist)+ - geom_histogram(aes(x = date_onset, fill = hospital))+ - scale_fill_brewer(type = "qual", palette = 1, na.value = "grey")+ - theme_half_open()+ - theme(legend.position = "bottom")+ +main_plot <- ggplot(data = linelist) + + geom_histogram(mapping = aes(x = date_onset, fill = hospital)) + + scale_fill_brewer(type = "qual", palette = 1, na.value = "grey") + + theme_minimal() + + theme(legend.position = "bottom") + labs(title = "Epidemic curve and outcomes by hospital") @@ -912,32 +877,21 @@ main_plot <- ggplot(data = linelist)+ inset_plot <- linelist %>% mutate(hospital = recode(hospital, "St. Mark's Maternity Hospital (SMMH)" = "St. Marks")) %>% count(hospital, outcome) %>% - ggplot()+ - geom_col(mapping = aes(x = hospital, y = n, fill = outcome))+ - scale_fill_brewer(type = "qual", palette = 4, na.value = "grey")+ - coord_flip()+ - theme_minimal()+ + ggplot() + + geom_col(mapping = aes(x = hospital, y = n, fill = outcome)) + + scale_fill_brewer(type = "qual", palette = 4, na.value = "grey") + + coord_flip() + + theme_minimal() + theme(legend.position = "none", - axis.title.y = element_blank())+ + axis.title.y = element_blank()) + labs(title = "Cases by outcome") # Combine main with inset -cowplot::ggdraw(main_plot)+ - draw_plot(inset_plot, - x = .6, y = .55, #x = .07, y = .65, - width = .4, height = .4) +main_plot + inset_element(inset_plot, 0.6, 0.5, 1, 1) ``` - - -This technique is explained more in these two vignettes: - -[Wilke lab](https://wilkelab.org/cowplot/articles/drawing_with_on_plots.html) -[draw_plot() documentation](https://www.rdocumentation.org/packages/cowplot/versions/1.1.1/topics/draw_plot) - - - +Further details on insetting plots is found [here](https://patchwork.data-imaginist.com/reference/inset_element.html). ## Dual axes {} @@ -1015,8 +969,6 @@ aligned_plotted # ``` - - @@ -1137,6 +1089,7 @@ Facets and labellers [Labellers](https://ggplot2.tidyverse.org/reference/labellers.html) Adjusting order with factors + [fct_reorder](https://forcats.tidyverse.org/reference/fct_reorder.html) [fct_inorder](https://forcats.tidyverse.org/reference/fct_inorder.html) [How to reorder a boxplot](https://cmdlinetips.com/2019/02/how-to-reorder-a-boxplot-in-r/) @@ -1155,6 +1108,8 @@ Labels Cheatsheets [Beautiful plotting with ggplot2](http://zevross.com/blog/2014/08/04/beautiful-plotting-in-r-a-ggplot2-cheatsheet-3/) +Plot alignment +[patchwork](https://patchwork.data-imaginist.com/index.html) @@ -1188,7 +1143,7 @@ Cheatsheets - + @@ -1199,12 +1154,12 @@ Cheatsheets - - + + - - + + diff --git a/new_pages/gis.qmd b/new_pages/gis.qmd index 0f26d0a6..77a3b259 100644 --- a/new_pages/gis.qmd +++ b/new_pages/gis.qmd @@ -27,15 +27,15 @@ Below we introduce some key terminology. For a thorough introduction to GIS and Some popular GIS software allow point-and-click interaction for map development and spatial analysis. These tools comes with advantages such as not needing to learn code and the ease of manually selecting and placing icons and features on a map. Here are two popular ones: -**ArcGIS** - A commercial GIS software developed by the company ESRI, which is very popular but quite expensive +**ArcGIS** - A commercial GIS software developed by the company ESRI, which is very popular but quite expensive. -**QGIS** - A free open-source GIS software that can do almost anything that ArcGIS can do. You can [download QGIS here](https://qgis.org/en/site/forusers/download.html) +**QGIS** - A free open-source GIS software that can do almost anything that ArcGIS can do. You can [download QGIS here](https://qgis.org/en/site/forusers/download.html). Using R as a GIS can seem more intimidating at first because instead of "point-and-click", it has a "command-line interface" (you must code to acquire the desired outcome). However, this is a major advantage if you need to repetitively produce maps or create an analysis that is reproducible. ### Spatial data {.unnumbered} -The two primary forms of spatial data used in GIS are vector and raster data: +The two primary forms of spatial data used in GIS are vector and raster data. **Vector Data** - The most common format of spatial data used in GIS, vector data are comprised of geometric features of vertices and paths. Vector spatial data can be further divided into three widely-used types: @@ -59,7 +59,7 @@ The shapefile will contain information about the features themselves, as well as * *Coordinate System* - There are many many different coordinate systems, so make sure you know which system your coordinates are from. Degrees of latitude/longitude are common, but you could also see [UTM](https://www.maptools.com/tutorials/utm/quick_guide) coordinates. - * *Units* - Know what the units are for your coordinate system (e.g. decimal degrees, meters) + * *Units* - Know what the units are for your coordinate system (e.g. decimal degrees, meters). * *Datum* - A particular modeled version of the Earth. These have been revised over the years, so ensure that your map layers are using the same datum. @@ -76,7 +76,7 @@ There are a couple of key items you will need to have and to think about to make * If your dataset is not in a spatial format you will also need a **reference dataset**. Reference data consists of the spatial representation of the data and the related **attributes**, which would include material containing the location and address information of specific features. - + If you are working with pre-defined geographic boundaries (for example, administrative regions), reference shapefiles are often freely available to download from a government agency or data sharing organization. When in doubt, a good place to start is to Google “[regions] shapefile” + + If you are working with pre-defined geographic boundaries (for example, administrative regions), reference shapefiles are often freely available to download from a government agency or data sharing organization. When in doubt, a good place to start is to Google “[regions] shapefile”. + If you have address information, but no latitude and longitude, you may need to use a **geocoding engine** to get the spatial reference data for your records. @@ -108,7 +108,7 @@ knitr::include_graphics(here::here("images", "gis_heatmap.png")) # proportional symbols img here ``` -You can also combine several different types of visualizations to show complex geographic patterns. For example, the cases (dots) in the map below are colored according to their closest health facility (see legend). The large red circles show *health facility catchment areas* of a certain radius, and the bright red case-dots those that were outside any catchment range: +You can also combine several different types of visualizations to show complex geographic patterns. For example, the cases (dots) in the map below are colored according to their closest health facility (see legend). The large black circles show *health facility catchment areas* of a certain radius, and the bright red case-dots those that were outside any catchment range: ```{r, fig.align = "center", echo=F} knitr::include_graphics(here::here("images", "gis_hf_catchment.png")) @@ -133,6 +133,8 @@ pacman::p_load( tmap, # to produce simple maps, works for both interactive and static maps janitor, # to clean column names OpenStreetMap, # to add OSM basemap in ggplot map + maptiles, # for creating basemaps + tidyterra, # for plotting basemaps spdep # spatial statistics ) ``` @@ -148,7 +150,7 @@ Since we are taking a random sample of the cases, your results may look slightly Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import clean case linelist linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -192,9 +194,9 @@ In advance, we have downloaded all administrative boundaries for Sierra Leone fr Now we are going to do the following to save the Admin Level 3 shapefile in R: -1) Import the shapefile -2) Clean the column names -3) Filter rows to keep only areas of interest +1) Import the shapefile +2) Clean the column names +3) Filter rows to keep only areas of interest To import a shapefile we use the `read_sf()` function from **sf**. It is provided the filepath via `here()`. - in our case the file is within our R project in the "data", "gis", and "shp" subfolders, with filename "sle_adm3.shp" (see pages on [Import and export](importing.qmd) and [R projects](r_projects.qmd) for more information). You will need to provide your own file path. @@ -237,7 +239,7 @@ Here is what the population file looks like. Scroll to the right to see how each ```{r message=FALSE, echo=F} # display the population as a table -DT::datatable(head(sle_adm3_pop, 50), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) +DT::datatable(head(sle_adm3_pop, 50), rownames = FALSE, options = list(pageLength = 5, scrollX = T), class = 'white-space: nowrap' ) ``` @@ -245,7 +247,7 @@ DT::datatable(head(sle_adm3_pop, 50), rownames = FALSE, options = list(pageLengt **Sierra Leone: Health facility data from OpenStreetMap** -Again we have downloaded the locations of health facilities from HDX [here](https://data.humdata.org/dataset/hotosm_sierra_leone_health_facilities) or via instructions in the [Download handbook and data](#data-used) page. +Again we have downloaded the locations of health facilities from HDX [here](https://data.humdata.org/dataset/hotosm_sle_health_facilities) or via instructions in the [Download handbook and data](#data-used) page. We import the facility points shapefile with `read_sf()`, again clean the column names, and then filter to keep only the points tagged as either "hospital", "clinic", or "doctors". @@ -261,7 +263,7 @@ Here is the resulting dataframe - *scroll right* to see the facility name and `g ```{r message=FALSE, echo=F} # display the population as a table -DT::datatable(head(sle_hf, 50), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) +DT::datatable(head(sle_hf, 50), rownames = FALSE, options = list(pageLength = 5, scrollX = T), class = 'white-space: nowrap' ) ``` @@ -282,12 +284,13 @@ The package **tmap** offers simple mapping capabilities for both static ("plot" tmap_mode("plot") # choose either "view" or "plot" ``` -Below, the points are plotted alone.`tm_shape()` is provided with the `linelist_sf` objects. We then add points via `tm_dots()`, specifying the size and color. Because `linelist_sf` is an sf object, we have already designated the two columns that contain the lat/long coordinates and the coordinate reference system (CRS): +Below, the points are plotted alone. The function `tm_shape()` is provided with the `linelist_sf` objects. We then add points via `tm_dots()`, specifying the size and color. Because `linelist_sf` is an sf object, we have already designated the two columns that contain the lat/long coordinates and the coordinate reference system (CRS): ```{r, warning = F, message=F} # Just the cases (points) -tm_shape(linelist_sf) + tm_dots(size=0.08, col='blue') +tm_shape(linelist_sf) + + tm_dots(size = 0.08, col='blue') ``` Alone, the points do not tell us much. So we should also map the administrative boundaries: @@ -299,7 +302,7 @@ With the `bbox = ` argument (bbox stands for "bounding box") we can specify the ```{r, out.width = c('50%', '50%'), fig.show='hold', warning=F, message=F} # Just the administrative boundaries (polygons) tm_shape(sle_adm3) + # admin boundaries shapefile - tm_polygons(col = "#F7F7F7")+ # show polygons in light grey + tm_polygons(col = "#F7F7F7") + # show polygons in light grey tm_borders(col = "#000000", # show borders with color and line weight lwd = 2) + tm_text("admin3name") # column text to display for each polygon @@ -323,9 +326,9 @@ And now both points and polygons together: tm_shape(sle_adm3, bbox = c(-13.3, 8.43, -13.2, 8.5)) + # tm_polygons(col = "#F7F7F7") + tm_borders(col = "#000000", lwd = 2) + - tm_text("admin3name")+ + tm_text("admin3name") + tm_shape(linelist_sf) + - tm_dots(size=0.08, col='blue', alpha = 0.5) + + tm_dots(size = 0.08, col = 'blue', alpha = 0.5) + tm_layout(title = "Distribution of Ebola cases") # give title to map ``` @@ -342,7 +345,7 @@ To read a good comparison of mapping options in R, see this [blog post](https:// You may be familiar with *joining* data from one dataset to another one. Several methods are discussed in the [Joining data](joining_matching.qmd) page of this handbook. A spatial join serves a similar purpose but leverages spatial relationships. Instead of relying on common values in columns to correctly match observations, you can utilize their spatial relationships, such as one feature being *within* another, or *the nearest neighbor* to another, or within a *buffer* of a certain radius from another, etc. -The **sf** package offers various methods for spatial joins. See more documentation about the st_join method and spatial join types in this [reference](https://r-spatial.github.io/sf/reference/geos_binary_pred.html). +The **sf** package offers various methods for spatial joins. See more documentation about the `st_join()` method and spatial join types in this [reference](https://r-spatial.github.io/sf/reference/geos_binary_pred.html). ### Points in polygon {.unnumbered} @@ -352,13 +355,12 @@ Here is an interesting conundrum: the case linelist does not contain any informa Below, we will spatially intersect our case locations (points) with the ADM3 boundaries (polygons): -1) Begin with the linelist (points) -2) Spatial join to the boundaries, setting the type of join at "st_intersects" -3) Use `select()` to keep only certain of the new administrative boundary columns +1) Begin with the linelist (points) +2) Spatial join to the boundaries, setting the type of join at "st_intersects" +3) Use `select()` to keep only certain of the new administrative boundary columns ```{r, warning=F, message=F} linelist_adm <- linelist_sf %>% - # join the administrative boundary file to the linelist, based on spatial intersection sf::st_join(sle_adm3, join = st_intersects) ``` @@ -367,10 +369,8 @@ All the columns from `sle_adms` have been added to the linelist! Each case now h ```{r, warning=F, message=F} linelist_adm <- linelist_sf %>% - # join the administrative boundary file to the linelist, based on spatial intersection sf::st_join(sle_adm3, join = st_intersects) %>% - # Keep the old column names and two new admin ones of interest select(names(linelist_sf), admin3name, admin3pcod) ``` @@ -379,7 +379,8 @@ Below, just for display purposes you can see the first ten cases and that their ```{r, warning=F, message=F} # Now you will see the ADM3 names attached to each case -linelist_adm %>% select(case_id, admin3name, admin3pcod) +linelist_adm %>% + select(case_id, admin3name, admin3pcod) ``` Now we can describe our cases by administrative unit - something we were not able to do before the spatial join! @@ -403,10 +404,10 @@ In this example, we begin the `ggplot()` with the `linelist_adm`, so that we can ggplot( data = linelist_adm, # begin with linelist containing admin unit info mapping = aes( - x = fct_rev(fct_infreq(admin3name))))+ # x-axis is admin units, ordered by frequency (reversed) - geom_bar()+ # create bars, height is number of rows - coord_flip()+ # flip X and Y axes for easier reading of adm units - theme_classic()+ # simplify background + x = fct_rev(fct_infreq(admin3name)))) + # x-axis is admin units, ordered by frequency (reversed) + geom_bar() + # create bars, height is number of rows + coord_flip() + # flip X and Y axes for easier reading of adm units + theme_classic() + # simplify background labs( # titles and labels x = "Admin level 3", y = "Number of cases", @@ -425,8 +426,8 @@ It might be useful to know where the health facilities are located in relation t We can use the *st_nearest_feature* join method from the `st_join()` function (**sf** package) to visualize the closest health facility to individual cases. -1) We begin with the shapefile linelist `linelist_sf` -2) We spatially join with `sle_hf`, which is the locations of health facilities and clinics (points) +1) We begin with the shapefile linelist `linelist_sf` +2) We spatially join with `sle_hf`, which is the locations of health facilities and clinics (points) ```{r, warning=F, message=F} # Closest health facility to each case @@ -436,24 +437,25 @@ linelist_sf_hf <- linelist_sf %>% # begin with linelist shapefi rename("nearest_clinic" = "name") # re-name for clarity ``` -We can see below (first 50 rows) that the each case now has data on the nearest clinic/hospital +We can see below (first 50 rows) that the each case now has data on the nearest clinic/hospital. ```{r message=FALSE, echo=F} DT::datatable(head(linelist_sf_hf, 50), rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) ``` -We can see that "Den Clinic" is the closest health facility for about ~30% of the cases. +We can see that "Den Clinic" is the closest health facility for about 34.6% of the cases. ```{r} # Count cases by health facility -hf_catchment <- linelist_sf_hf %>% # begin with linelist including nearest clinic data - as.data.frame() %>% # convert from shapefile to dataframe - count(nearest_clinic, # count rows by "name" (of clinic) - name = "case_n") %>% # assign new counts column as "case_n" - arrange(desc(case_n)) # arrange in descending order - -hf_catchment # print to console +hf_catchment <- linelist_sf_hf %>% # begin with linelist including nearest clinic data + as.data.frame() %>% # convert from shapefile to dataframe + count(nearest_clinic, # count rows by "name" (of clinic) + name = "case_n") %>% # assign new counts column as "case_n" + arrange(desc(case_n)) %>% # arrange in descending order + mutate(percent = case_n/sum(case_n)) #Calculate % of cases per nearest clinic + +hf_catchment # print to console ``` To visualize the results, we can use **tmap** - this time interactive mode for easier viewing @@ -469,7 +471,7 @@ tm_shape(sle_hf) + # plot clinic facilities in large black do tm_dots(size=0.3, col='black', alpha = 0.4) + tm_text("name") + # overlay with name of facility tm_view(set.view = c(-13.2284, 8.4699, 13), # adjust zoom (center coords, zoom) - set.zoom.limits = c(13,14))+ + set.zoom.limits = c(13,14)) + tm_layout(title = "Cases, colored by nearest clinic") ``` @@ -487,7 +489,7 @@ See more information about map projections and coordinate systems at this [esri ```{r, warning=F, message=F} sle_hf_2k <- sle_hf %>% - st_buffer(dist=0.02) # decimal degrees translating to approximately 2.5km + st_buffer(dist = 0.02) # decimal degrees translating to approximately 2.5km ``` Below we plot the buffer zones themselves, with the : @@ -496,19 +498,19 @@ Below we plot the buffer zones themselves, with the : tmap_mode("plot") # Create circular buffers tm_shape(sle_hf_2k) + - tm_borders(col = "black", lwd = 2)+ + tm_borders(col = "black", lwd = 2) + tm_shape(sle_hf) + # plot clinic facilities in large red dots - tm_dots(size=0.3, col='black') + tm_dots(size = 0.3, col = 'black') ``` -**Second*, we intersect these buffers with the cases (points) using `st_join()` and the join type of *st_intersects*. That is, the data from the buffers are joined to the points that they intersect with. +*Second*, we intersect these buffers with the cases (points) using `st_join()` and the join type of *st_intersects*. That is, the data from the buffers are joined to the points that they intersect with. ```{r, warning=F, message=F} # Intersect the cases with the buffers linelist_sf_hf_2k <- linelist_sf_hf %>% st_join(sle_hf_2k, join = st_intersects, left = TRUE) %>% - filter(osm_id.x==osm_id.y | is.na(osm_id.y)) %>% + filter(osm_id.x == osm_id.y | is.na(osm_id.y)) %>% select(case_id, osm_id.x, nearest_clinic, amenity.x, osm_id.y) ``` @@ -528,11 +530,11 @@ tmap_mode("view") # First display the cases in points tm_shape(linelist_sf_hf) + - tm_dots(size=0.08, col='nearest_clinic') + + tm_dots(size = 0.08, col = 'nearest_clinic') + # plot clinic facilities in large black dots tm_shape(sle_hf) + - tm_dots(size=0.3, col='black')+ + tm_dots(size = 0.3, col = 'black') + # Then overlay the health facility buffers in polylines tm_shape(sle_hf_2k) + @@ -540,9 +542,10 @@ tm_shape(sle_hf_2k) + # Highlight cases that are not part of any health facility buffers # in red dots -tm_shape(linelist_sf_hf_2k %>% filter(is.na(osm_id.y))) + - tm_dots(size=0.1, col='red') + -tm_view(set.view = c(-13.2284,8.4699, 13), set.zoom.limits = c(13,14))+ +tm_shape(linelist_sf_hf_2k %>% + filter(is.na(osm_id.y))) + + tm_dots(size = 0.1, col='red') + +tm_view(set.view = c(-13.2284,8.4699, 13), set.zoom.limits = c(13,14)) + # add title tm_layout(title = "Cases by clinic catchment area") @@ -583,9 +586,9 @@ Since we also have population data by ADM3, we can add this information to the * We begin with the dataframe created in the previous step `case_adm3`, which is a summary table of each administrative unit and its number of cases. -1) The population data `sle_adm3_pop` are joined using a `left_join()` from **dplyr** on the basis of common values across column `admin3pcod` in the `case_adm3` dataframe, and column `adm_pcode` in the `sle_adm3_pop` dataframe. See page on [Joining data](joining_matching.qmd)). -2) `select()` is applied to the new dataframe, to keep only the useful columns - `total` is total population -3) Cases per 10,000 populaton is calculated as a new column with `mutate()` +1) The population data `sle_adm3_pop` are joined using a `left_join()` from **dplyr** on the basis of common values across column `admin3pcod` in the `case_adm3` dataframe, and column `adm_pcode` in the `sle_adm3_pop` dataframe. See page on [Joining data](joining_matching.qmd)) +2) `select()` is applied to the new dataframe, to keep only the useful columns - `total` is total population +3) Cases per 10,000 populaton is calculated as a new column with `mutate()` ```{r} @@ -599,11 +602,11 @@ case_adm3 <- case_adm3 %>% case_adm3 # print to console for viewing ``` -Join this table with the ADM3 polygons shapefile for mapping +Join this table with the ADM3 polygons shapefile for mapping. ```{r, warning=F, message=F} case_adm3_sf <- case_adm3 %>% # begin with cases & rate by admin unit - left_join(sle_adm3, by="admin3pcod") %>% # join to shapefile data by common column + left_join(sle_adm3, by = "admin3pcod") %>% # join to shapefile data by common column select(objectid, admin3pcod, # keep only certain columns of interest admin3name = admin3name.x, # clean name of one column admin2name, admin1name, @@ -615,7 +618,7 @@ case_adm3_sf <- case_adm3 %>% # begin with cases & rate by admin ``` -Mapping the results +Mapping the results. ```{r, message=F, warning=F} # tmap mode @@ -663,32 +666,32 @@ select(sle_adm3_dat, admin3name.x, cases) # print selected variables to console To make a column chart of case counts by region, using **ggplot2**, we could then call `geom_col()` as follows: ```{r, fig.align = "center"} -ggplot(data=sle_adm3_dat) + - geom_col(aes(x=fct_reorder(admin3name.x, cases, .desc=T), # reorder x axis by descending 'cases' - y=cases)) + # y axis is number of cases by region +ggplot(data = sle_adm3_dat) + + geom_col(aes(x = fct_reorder(admin3name.x, cases, .desc=T), # reorder x axis by descending 'cases' + y = cases)) + # y axis is number of cases by region theme_bw() + labs( # set figure text title="Number of cases, by administrative unit", x="Admin level 3", y="Number of cases" ) + - guides(x=guide_axis(angle=45)) # angle x-axis labels 45 degrees to fit better + guides(x = guide_axis(angle = 45)) # angle x-axis labels 45 degrees to fit better ``` If we want to use **ggplot2** to instead make a choropleth map of case counts, we can use similar syntax to call the `geom_sf()` function: ```{r, fig.align = "center"} -ggplot(data=sle_adm3_dat) + - geom_sf(aes(fill=cases)) # set fill to vary by case count variable +ggplot(data = sle_adm3_dat) + + geom_sf(aes(fill = cases)) # set fill to vary by case count variable ``` We can then customize the appearance of our map using grammar that is consistent across **ggplot2**, for example: ```{r, fig.align = "center"} -ggplot(data=sle_adm3_dat) + - geom_sf(aes(fill=cases)) + - scale_fill_continuous(high="#54278f", low="#f2f0f7") + # change color gradient +ggplot(data = sle_adm3_dat) + + geom_sf(aes(fill = cases)) + + scale_fill_continuous(high = "#54278f", low = "#f2f0f7") + # change color gradient theme_bw() + labs(title = "Number of cases, by administrative unit", # set figure text subtitle = "Admin level 3" @@ -710,61 +713,49 @@ Below we describe how to achieve a basemap for a **ggplot2** map using OpenStree [**OpenStreetMap**](https://en.wikipedia.org/wiki/OpenStreetMap) is a collaborative project to create a free editable map of the world. The underlying geolocation data (e.g. locations of cities, roads, natural features, airports, schools, hospitals, roads etc) are considered the primary output of the project. -First we load the **OpenStreetMap** package, from which we will get our basemap. +To do this, first we load in the packages we'll need. These are the **maptiles** package, which we will use to get the OpenStreetMap base layer, and the **tidyterra** package for plotting the maptiles object. + +The function `get_tiles()` from the **maptiles** package can accept a variety of different inputs. These include shapefiles, as an sf object, bbox objects and SpatExtent objects. For a full list, type `?get_tiles` into your console. -Then, we create the object `map`, which we define using the function `openmap()` from **OpenStreetMap** package ([documentation](https://www.rdocumentation.org/packages/OpenStreetMap/versions/0.3.4/topics/openmap)). We provide the following: +There are a number of different ways to customise the output of `get_tiles()`, including changing the map provider, and saving the output so it can be accessed offline (by specifying a folder location in the `cachedir = ` argument). -* `upperLeft` and `lowerRight` Two coordinate pairs specifying the limits of the basemap tile - * In this case we've put in the max and min from the linelist rows, so the map will respond dynamically to the data -* `zoom = ` (if null it is determined automatically) -* `type =` which type of basemap - we have listed several possibilities here and the code is currently using the first one (`[1]`) "osm" -* `mergeTiles = ` we chose TRUE so the basetiles are all merged into one +Here we are going to create a map using the coordinates of the area we are interested in. To provide these in a format that can be used by `get_tiles()` we will wrap the coordinates with the function `ext()` from the **terra** package, which should be loaded with **tidyterra**. Then we will use the function `geom_spatraster_rgb()` to display the map. +**Note: If you right click on [Google Maps](https://www.google.com/maps) it will display the coordinates of the point**. ```{r, message=FALSE, warning=FALSE} # load package -pacman::p_load(OpenStreetMap) - -# Fit basemap by range of lat/long coordinates. Choose tile type -map <- OpenStreetMap::openmap( - upperLeft = c(max(linelist$lat, na.rm=T), max(linelist$lon, na.rm=T)), # limits of basemap tile - lowerRight = c(min(linelist$lat, na.rm=T), min(linelist$lon, na.rm=T)), - zoom = NULL, - type = c("osm", "stamen-toner", "stamen-terrain", "stamen-watercolor", "esri","esri-topo")[1]) -``` +pacman::p_load( + maptiles, + tidyterra +) -If we plot this basemap right now, using `autoplot.OpenStreetMap()` from **OpenStreetMap** package, you see that the units on the axes are not latitude/longitude coordinates. It is using a different coordinate system. To correctly display the case residences (which are stored in lat/long), this must be changed. +#The coordinate extent of the area we are looking at, by taking the range of longditude and latitudes +#Values correspond as xmin, xmax, ymin, ymax +coordinates <- c(min(linelist$lon), + max(linelist$lon), + min(linelist$lat), + max(linelist$lat)) -```{r, warning=F, message=F} -autoplot.OpenStreetMap(map) -``` -Thus, we want to convert the map to latitude/longitude with the `openproj()` function from **OpenStreetMap** package. We provide the basemap `map` and also provide the Coordinate Reference System (CRS) we want. We do this by providing the "proj.4" character string for the WGS 1984 projection, but you can provide the CRS in other ways as well. (see [this page](https://www.earthdatascience.org/courses/earth-analytics/spatial-data-r/understand-epsg-wkt-and-other-crs-definition-file-types/) to better understand what a proj.4 string is) +#Get the basemap +basemap <- get_tiles(terra::ext(coordinates), + crop = T, project = T) -```{r, warning=F, message=F} -# Projection WGS84 -map_latlon <- openproj(map, projection = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs") -``` +# Plot the tile +ggplot() + + geom_spatraster_rgb( + data = basemap + ) -Now when we create the plot we see that along the axes are latitude and longitude coordinate. The coordinate system has been converted. Now our cases will plot correctly if overlaid! - -```{r, warning=F, message=F} -# Plot map. Must use "autoplot" in order to work with ggplot -autoplot.OpenStreetMap(map_latlon) ``` -See the tutorials [here](http://data-analytics.net/cep/Schedule_files/geospatial.html) and [here](https://www.rdocumentation.org/packages/OpenStreetMap/versions/0.3.4/topics/autoplot.OpenStreetMap) for more info. - - - - - ## Contoured density heatmaps {} Below we describe how to achieve a contoured density heatmap of cases, over a basemap, beginning with a linelist (one row per case). -1) Create basemap tile from OpenStreetMap, as described above -2) Plot the cases from `linelist` using the latitude and longitude columns -3) Convert the points to a density heatmap with `stat_density_2d()` from **ggplot2**, +1) Create basemap tile, as described above +2) Plot the cases from `linelist` using the latitude and longitude columns +3) Convert the points to a density heatmap with `stat_density_2d()` from **ggplot2** When we have a basemap with lat/long coordinates, we can plot our cases on top using the lat/long coordinates of their residence. @@ -773,8 +764,11 @@ Building on the function `autoplot.OpenStreetMap()` to create the basemap, **ggp ```{r, warning=F, message=F} # Plot map. Must be autoplotted to work with ggplot -autoplot.OpenStreetMap(map_latlon)+ # begin with the basemap - geom_point( # add xy points from linelist lon and lat columns +ggplot() + + geom_spatraster_rgb( + data = basemap + ) + + geom_point( # add xy points from linelist lon and lat columns data = linelist, aes(x = lon, y = lat), size = 1, @@ -790,8 +784,10 @@ The map above might be difficult to interpret, especially with the points overla ```{r, warning=F, message=F} # begin with the basemap -autoplot.OpenStreetMap(map_latlon)+ - +ggplot() + + geom_spatraster_rgb( + data = basemap + ) + # add the density plot ggplot2::stat_density_2d( data = linelist, @@ -806,7 +802,7 @@ autoplot.OpenStreetMap(map_latlon)+ show.legend = F) + # specify color scale - scale_fill_gradient(low = "black", high = "red")+ + scale_fill_gradient(low = "black", high = "red") + # labels labs(x = "Longitude", @@ -839,12 +835,12 @@ Now, we simply introduce facetting via **ggplot2** to the density heatmap. `face ```{r, warning=F, message=F} -# packages -pacman::p_load(OpenStreetMap, tidyverse) # begin with the basemap -autoplot.OpenStreetMap(map_latlon)+ - +ggplot() + + geom_spatraster_rgb( + data = basemap + ) + # add the density plot ggplot2::stat_density_2d( data = linelist, @@ -859,12 +855,12 @@ autoplot.OpenStreetMap(map_latlon)+ show.legend = F) + # specify color scale - scale_fill_gradient(low = "black", high = "red")+ + scale_fill_gradient(low = "black", high = "red") + # labels labs(x = "Longitude", y = "Latitude", - title = "Distribution of cumulative cases over time")+ + title = "Distribution of cumulative cases over time") + # facet the plot by month-year of onset facet_wrap(~ date_onset_ym, ncol = 4) @@ -884,7 +880,7 @@ Before we can calculate any spatial statistics, we need to specify the relations We can quantify adjacency relationships between administrative region polygons in the `sle_adm3` data we have been using with the **spdep** package. We will specify *queen* contiguity, which means that regions will be neighbors if they share at least one point along their borders. The alternative would be *rook* contiguity, which requires that regions share an edge - in our case, with irregular polygons, the distinction is trivial, but in some cases the choice between queen and rook can be influential. ```{r} -sle_nb <- spdep::poly2nb(sle_adm3_dat, queen=T) # create neighbors +sle_nb <- spdep::poly2nb(sle_adm3_dat, queen = T) # create neighbors sle_adjmat <- spdep::nb2mat(sle_nb) # create matrix summarizing neighbor relationships sle_listw <- spdep::nb2listw(sle_nb) # create listw (list of weights) object -- we will need this later @@ -897,14 +893,14 @@ The matrix printed above shows the relationships between the 9 regions in our `s A better way to visualize these neighbor relationships is by plotting them: ```{r, fig.align='center', results='hide'} plot(sle_adm3_dat$geometry) + # plot region boundaries - spdep::plot.nb(sle_nb,as(sle_adm3_dat, 'Spatial'), col='grey', add=T) # add neighbor relationships + spdep::plot.nb(sle_nb,as(sle_adm3_dat, 'Spatial'), col = 'grey', add = T) # add neighbor relationships ``` We have used an adjacency approach to identify neighboring polygons; the neighbors we identified are also sometimes called **contiguity-based neighbors**. But this is just one way of choosing which regions are expected to have a geographic relationship. The most common alternative approaches for identifying geographic relationships generate **distance-based neighbors**; briefly, these are: - * **K-nearest neighbors** - Based on the distance between centroids (the geographically-weighted center of each polygon region), select the *n* closest regions as neighbors. A maximum-distance proximity threshold may also be specified. In **spdep**, you can use `knearneigh()` (see [documentation](https://r-spatial.github.io/spdep/reference/knearneigh.html)). + * **K-nearest neighbors** - Based on the distance between centroids (the geographically-weighted center of each polygon region), select the *n* closest regions as neighbors. A maximum-distance proximity threshold may also be specified. In **spdep**, you can use `knearneigh()` (see [documentation](https://r-spatial.github.io/spdep/reference/knearneigh.html)) - * **Distance threshold neighbors** - Select all neighbors within a distance threshold. In **spdep**, these neighbor relationships can be identified using `dnearneigh()` (see [documentation](https://www.rdocumentation.org/packages/spdep/versions/1.1-7/topics/dnearneigh)). + * **Distance threshold neighbors** - Select all neighbors within a distance threshold. In **spdep**, these neighbor relationships can be identified using `dnearneigh()` (see [documentation](https://www.rdocumentation.org/packages/spdep/versions/1.1-7/topics/dnearneigh)) ### Spatial autocorrelation {.unnumbered} @@ -916,11 +912,11 @@ For an example, we will calculate a Moran's I statistic to quantify the spatial ```{r} moran_i <-spdep::moran.test(sle_adm3_dat$cases, # numeric vector with variable of interest - listw=sle_listw) # listw object summarizing neighbor relationships + listw = sle_listw) # listw object summarizing neighbor relationships moran_i # print results of Moran's I test ``` -The output from the `moran.test()` function shows us a Moran I statistic of ` round(moran_i$estimate[1],2)`. This indicates the presence of spatial autocorrelation in our data - specifically, that regions with similar numbers of Ebola cases are likely to be close together. The p-value provided by `moran.test()` is generated by comparison to the expectation under null hypothesis of no spatial autocorrelation, and can be used if you need to report the results of a formal hypothesis test. +The output from the `moran.test()` function shows us a Moran I statistic of `r round(moran_i$estimate[1], 2)`. This indicates the presence of spatial autocorrelation in our data - specifically, that regions with similar numbers of Ebola cases are likely to be close together. The p-value provided by `moran.test()` is generated by comparison to the expectation under null hypothesis of no spatial autocorrelation, and can be used if you need to report the results of a formal hypothesis test. **Local Moran's I** - We can decompose the (global) Moran's I statistic calculated above to identify *localized* spatial autocorrelation; that is, to identify specific clusters in our data. This statistic, which is sometimes called a **Local Indicator of Spatial Association (LISA)** statistic, summarizes the extent of spatial autocorrelation around each individual region. It can be useful for finding "hot" and "cold" spots on the map. @@ -929,24 +925,24 @@ To show an example, we can calculate and map Local Moran's I for the Ebola case # calculate local Moran's I local_moran <- spdep::localmoran( sle_adm3_dat$cases, # variable of interest - listw=sle_listw # listw object with neighbor weights + listw = sle_listw # listw object with neighbor weights ) # join results to sf data sle_adm3_dat<- cbind(sle_adm3_dat, local_moran) # plot map -ggplot(data=sle_adm3_dat) + - geom_sf(aes(fill=Ii)) + +ggplot(data = sle_adm3_dat) + + geom_sf(aes(fill = Ii)) + theme_bw() + - scale_fill_gradient2(low="#2c7bb6", mid="#ffffbf", high="#d7191c", - name="Local Moran's I") + - labs(title="Local Moran's I statistic for Ebola cases", - subtitle="Admin level 3 regions, Sierra Leone") + scale_fill_gradient2(low = "#2c7bb6", mid = "#ffffbf", high = "#d7191c", + name = "Local Moran's I") + + labs(title = "Local Moran's I statistic for Ebola cases", + subtitle = "Admin level 3 regions, Sierra Leone") ``` -**Getis-Ord Gi\*** - This is another statistic that is commonly used for hotspot analysis; in large part, the popularity of this statistic relates to its use in the Hot Spot Analysis tool in ArcGIS. It is based on the assumption that typically, the difference in a variable's value between neighboring regions should follow a normal distribution. It uses a z-score approach to identify regions that have significantly higher (hot spot) or significantly lower (cold spot) values of a specified variable, compared to their neighbors. +**Getis-Ord Gi\** - This is another statistic that is commonly used for hotspot analysis; in large part, the popularity of this statistic relates to its use in the Hot Spot Analysis tool in ArcGIS. It is based on the assumption that typically, the difference in a variable's value between neighboring regions should follow a normal distribution. It uses a z-score approach to identify regions that have significantly higher (hot spot) or significantly lower (cold spot) values of a specified variable, compared to their neighbors. We can calculate and map the Gi* statistic using the `localG()` function from **spdep**: @@ -962,17 +958,17 @@ sle_adm3_dat$getis_ord <- as.numeric(getis_ord) # plot map ggplot(data=sle_adm3_dat) + - geom_sf(aes(fill=getis_ord)) + + geom_sf(aes(fill = getis_ord)) + theme_bw() + - scale_fill_gradient2(low="#2c7bb6", mid="#ffffbf", high="#d7191c", - name="Gi*") + - labs(title="Getis-Ord Gi* statistic for Ebola cases", - subtitle="Admin level 3 regions, Sierra Leone") + scale_fill_gradient2(low="#2c7bb6", mid = "#ffffbf", high = "#d7191c", + name = "Gi*") + + labs(title = "Getis-Ord Gi* statistic for Ebola cases", + subtitle = "Admin level 3 regions, Sierra Leone") ``` -As you can see, the map of Getis-Ord Gi* looks slightly different from the map of Local Moran's I produced earlier. This reflects that the method used to calculate these two statistics are slightly different; which one you should use depends on your specific use case and the research question of interest. +As you can see, the map of Getis-Ord Gi looks slightly different from the map of Local Moran's I produced earlier. This reflects that the method used to calculate these two statistics are slightly different; which one you should use depends on your specific use case and the research question of interest. **Lee's L test** - This is a statistical test for bivariate spatial correlation. It allows you to test whether the spatial pattern for a given variable *x* is similar to the spatial pattern of another variable, *y*, that is hypothesized to be related spatially to *x*. @@ -987,25 +983,27 @@ We can quickly visualize the spatial patterns of the two variables side by side, ```{r, fig.align='center', warning=F, message=F} tmap_mode("plot") -cases_map <- tm_shape(sle_adm3_dat) + tm_polygons("cases") + tm_layout(main.title="Cases") -pop_map <- tm_shape(sle_adm3_dat) + tm_polygons("population") + tm_layout(main.title="Population") +cases_map <- tm_shape(sle_adm3_dat) + + tm_polygons("cases") + tm_layout(main.title = "Cases") +pop_map <- tm_shape(sle_adm3_dat) + tm_polygons("population") + + tm_layout(main.title = "Population") -tmap_arrange(cases_map, pop_map, ncol=2) # arrange into 2x1 facets +tmap_arrange(cases_map, pop_map, ncol = 2) # arrange into 2x1 facets ``` Visually, the patterns seem dissimilar. We can use the `lee.test()` function in **spdep** to test statistically whether the pattern of spatial autocorrelation in the two variables is related. The L statistic will be close to 0 if there is no correlation between the patterns, close to 1 if there is a strong positive correlation (i.e. the patterns are similar), and close to -1 if there is a strong negative correlation (i.e. the patterns are inverse). ```{r, warning=F, message=F} lee_test <- spdep::lee.test( - x=sle_adm3_dat$cases, # variable 1 to compare - y=sle_adm3_dat$population, # variable 2 to compare - listw=sle_listw # listw object with neighbor weights + x = sle_adm3_dat$cases, # variable 1 to compare + y = sle_adm3_dat$population, # variable 2 to compare + listw = sle_listw # listw object with neighbor weights ) lee_test ``` -The output above shows that the Lee's L statistic for our two variables was ` round(lee_test$estimate[1],2)`, which indicates weak negative correlation. This confirms our visual assessment that the pattern of cases and population are not related to one another, and provides evidence that the spatial pattern of cases is not strictly a result of population density in high-risk areas. +The output above shows that the Lee's L statistic for our two variables was `r round(lee_test$estimate[1], 2)`, which indicates weak negative correlation. This confirms our visual assessment that the pattern of cases and population are not related to one another, and provides evidence that the spatial pattern of cases is not strictly a result of population density in high-risk areas. The Lee L statistic can be useful for making these kinds of inferences about the relationship between spatially distributed variables; however, to describe the nature of the relationship between two variables in more detail, or adjust for confounding, *spatial regression* techniques will be needed. These are described briefly in the following section. @@ -1051,5 +1049,8 @@ knitr::include_graphics(here::here("images", "gis_lmflowchart.jpg")) * **SpatialEpiApp** - a [Shiny app that is downloadable as an R package](https://github.com/Paula-Moraga/SpatialEpiApp), allowing you to provide your own data and conduct mapping, cluster analysis, and spatial statistics. +[Spatial Statistics for Data Science: Theory and Practice with R](https://www.paulamoraga.com/book-spatial/index.html) + +[Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny](https://www.paulamoraga.com/book-geospatial/) * An Introduction to Spatial Econometrics in R [workshop](http://www.econ.uiuc.edu/~lab/workshop/Spatial_in_R.html) diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-21-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-21-1.png new file mode 100644 index 00000000..94bca319 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-21-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-22-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-22-1.png new file mode 100644 index 00000000..7ba4248c Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-22-2.png b/new_pages/gis_files/figure-html/unnamed-chunk-22-2.png new file mode 100644 index 00000000..b9d0facc Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-22-2.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-23-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-23-1.png new file mode 100644 index 00000000..fa759950 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-23-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-28-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-28-1.png new file mode 100644 index 00000000..9a170fc5 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-28-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-34-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-34-1.png new file mode 100644 index 00000000..73857eb0 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-34-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-40-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-40-1.png new file mode 100644 index 00000000..c5d0abc7 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-40-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-41-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-41-1.png new file mode 100644 index 00000000..4b6cc0f6 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-41-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-44-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-44-1.png new file mode 100644 index 00000000..a7dcd31e Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-44-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-45-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-45-1.png new file mode 100644 index 00000000..b0d9f66d Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-45-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-46-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-46-1.png new file mode 100644 index 00000000..f534cfd1 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-46-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-48-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-48-1.png new file mode 100644 index 00000000..4517fc28 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-48-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-50-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-50-1.png new file mode 100644 index 00000000..6511b76a Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-50-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-51-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-51-1.png new file mode 100644 index 00000000..51586636 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-51-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-52-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-52-1.png new file mode 100644 index 00000000..c999ab91 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-52-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-54-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-54-1.png new file mode 100644 index 00000000..1ba754f6 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-54-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-56-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-56-1.png new file mode 100644 index 00000000..e4ae073a Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-56-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-58-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-58-1.png new file mode 100644 index 00000000..bfc625fa Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-58-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-59-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-59-1.png new file mode 100644 index 00000000..50d74744 Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-59-1.png differ diff --git a/new_pages/gis_files/figure-html/unnamed-chunk-61-1.png b/new_pages/gis_files/figure-html/unnamed-chunk-61-1.png new file mode 100644 index 00000000..2ca3679c Binary files /dev/null and b/new_pages/gis_files/figure-html/unnamed-chunk-61-1.png differ diff --git a/new_pages/grouping.html b/new_pages/grouping.html new file mode 100644 index 00000000..c62f5f72 --- /dev/null +++ b/new_pages/grouping.html @@ -0,0 +1,2067 @@ + + + + + + + + + +13  Grouping data – The Epidemiologist R Handbook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    13  Grouping data

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    This page covers how to group and aggregate data for descriptive analysis. It makes use of the tidyverse family of packages for common and easy-to-use functions.

    +

    Grouping data is a core component of data management and analysis. Grouped data statistically summarised by group, and can be plotted by group. Functions from the dplyr package (part of the tidyverse) make grouping and subsequent operations quite easy.

    +

    This page will address the following topics:

    +
      +
    • Group data with the group_by() function.
      +
    • +
    • Un-group data.
      +
    • +
    • summarise() grouped data with statistics.
      +
    • +
    • The difference between count() and tally().
      +
    • +
    • arrange() applied to grouped data.
      +
    • +
    • filter() applied to grouped data.
      +
    • +
    • mutate() applied to grouped data.
      +
    • +
    • select() applied to grouped data.
      +
    • +
    • The base R aggregate() command as an alternative.
    • +
    + +
    +

    13.1 Preparation

    +
    +

    Load packages

    +

    This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use. You can also load installed packages with library() from base R. See the page on R basics for more information on R packages.

    +
    +
    pacman::p_load(
    +  rio,       # to import data
    +  here,      # to locate files
    +  tidyverse, # to clean, handle, and plot the data (includes dplyr)
    +  janitor    # adding total rows and columns
    +  )  
    +
    +
    +
    +

    Import data

    +

    We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the “clean” linelist (as .rds file). The dataset is imported using the import() function from the rio package. See the page on Import and export for various ways to import data.

    +
    +
    linelist <- import("linelist_cleaned.rds")
    +
    +

    The first 50 rows of linelist:

    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    13.2 Grouping

    +

    The function group_by() from dplyr groups the rows by the unique values in the column specified to it. If multiple columns are specified, rows are grouped by the unique combinations of values across the columns. Each unique value (or combination of values) constitutes a group. Subsequent changes to the dataset or calculations can then be performed within the context of each group.

    +

    For example, the command below takes the linelist and groups the rows by unique values in the column outcome, saving the output as a new data frame ll_by_outcome. The grouping column(s) are placed inside the parentheses of the function group_by().

    +
    +
    ll_by_outcome <- linelist %>% 
    +  group_by(outcome)
    +
    +

    Note that there is no perceptible change to the dataset after running group_by(), until another dplyr verb such as mutate(), summarise(), or arrange() is applied on the “grouped” data frame.

    +

    You can however “see” the groupings by printing the data frame. When you print a grouped data frame, you will see it has been transformed into a tibble class object which, when printed, displays which groupings have been applied and how many groups there are - written just above the header row.

    +
    +
    # print to see which groups are active
    +ll_by_outcome
    +
    +
    # A tibble: 5,888 × 30
    +# Groups:   outcome [3]
    +   case_id generation date_infection date_onset date_hospitalisation
    +   <chr>        <dbl> <date>         <date>     <date>              
    + 1 5fe599           4 2014-05-08     2014-05-13 2014-05-15          
    + 2 8689b7           4 NA             2014-05-13 2014-05-14          
    + 3 11f8ea           2 NA             2014-05-16 2014-05-18          
    + 4 b8812a           3 2014-05-04     2014-05-18 2014-05-20          
    + 5 893f25           3 2014-05-18     2014-05-21 2014-05-22          
    + 6 be99c8           3 2014-05-03     2014-05-22 2014-05-23          
    + 7 07e3e8           4 2014-05-22     2014-05-27 2014-05-29          
    + 8 369449           4 2014-05-28     2014-06-02 2014-06-03          
    + 9 f393b4           4 NA             2014-06-05 2014-06-06          
    +10 1389ca           4 NA             2014-06-05 2014-06-07          
    +# ℹ 5,878 more rows
    +# ℹ 25 more variables: date_outcome <date>, outcome <chr>, gender <chr>,
    +#   age <dbl>, age_unit <chr>, age_years <dbl>, age_cat <fct>, age_cat5 <fct>,
    +#   hospital <chr>, lon <dbl>, lat <dbl>, infector <chr>, source <chr>,
    +#   wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>, chills <chr>,
    +#   cough <chr>, aches <chr>, vomit <chr>, temp <dbl>, time_admission <chr>,
    +#   bmi <dbl>, days_onset_hosp <dbl>
    +
    +
    +
    +

    Unique groups

    +

    The groups created reflect each unique combination of values across the grouping columns.

    +

    To see the groups and the number of rows in each group, pass the grouped data to tally(). To see just the unique groups without counts you can pass to group_keys().

    +

    See below that there are three unique values in the grouping column outcome: “Death”, “Recover”, and NA. See that there were nrow(linelist %>% filter(outcome == "Death")) deaths, nrow(linelist %>% filter(outcome == "Recover")) recoveries, and nrow(linelist %>% filter(is.na(outcome))) with no outcome recorded.

    +
    +
    linelist %>% 
    +  group_by(outcome) %>% 
    +  tally()
    +
    +
    # A tibble: 3 × 2
    +  outcome     n
    +  <chr>   <int>
    +1 Death    2582
    +2 Recover  1983
    +3 <NA>     1323
    +
    +
    +

    You can group by more than one column. Below, the data frame is grouped by outcome and gender, and then tallied. Note how each unique combination of outcome and gender is registered as its own group - including missing values for either column.

    +
    +
    linelist %>% 
    +  group_by(outcome, gender) %>% 
    +  tally()
    +
    +
    # A tibble: 9 × 3
    +# Groups:   outcome [3]
    +  outcome gender     n
    +  <chr>   <chr>  <int>
    +1 Death   f       1227
    +2 Death   m       1228
    +3 Death   <NA>     127
    +4 Recover f        953
    +5 Recover m        950
    +6 Recover <NA>      80
    +7 <NA>    f        627
    +8 <NA>    m        625
    +9 <NA>    <NA>      71
    +
    +
    +
    +
    +

    New columns

    +

    You can also create a new grouping column within the group_by() statement. This is equivalent to calling mutate() before the group_by(). For a quick tabulation this style can be handy, but for more clarity in your code consider creating this column in its own mutate() step and then piping to group_by().

    +
    +
    # group dat based on a binary column created *within* the group_by() command
    +linelist %>% 
    +  group_by(
    +    age_class = ifelse(age >= 18, "adult", "child")) %>% 
    +  tally(sort = T)
    +
    +
    # A tibble: 3 × 2
    +  age_class     n
    +  <chr>     <int>
    +1 child      3618
    +2 adult      2184
    +3 <NA>         86
    +
    +
    +
    +
    +

    Add/drop grouping columns

    +

    By default, if you run group_by() on data that are already grouped, the old groups will be removed and the new one(s) will apply. If you want to add new groups to the existing ones, include the argument .add = TRUE.

    +
    +
    # Grouped by outcome
    +by_outcome <- linelist %>% 
    +  group_by(outcome)
    +
    +# Add grouping by gender in addition
    +by_outcome_gender <- by_outcome %>% 
    +  group_by(gender, .add = TRUE)
    +
    +

    Keep all groups

    +

    If you group on a column of class factor there may be levels of the factor that are not currently present in the data. If you group on this column, by default those non-present levels are dropped and not included as groups. To change this so that all levels appear as groups (even if not present in the data), set .drop = FALSE in your group_by() command.

    +
    +
    +
    +

    13.3 Un-group

    +

    Data that have been grouped will remain grouped until specifically ungrouped via ungroup(). If you forget to ungroup, it can lead to incorrect calculations! Below is an example of removing all groupings:

    +
    +
    linelist %>% 
    +  group_by(outcome, gender) %>% 
    +  tally() %>% 
    +  ungroup()
    +
    +

    You can also remove grouping for only specific columns, by placing the column name inside ungroup().

    +
    +
    linelist %>% 
    +  group_by(outcome, gender) %>% 
    +  tally() %>% 
    +  ungroup(gender) # remove the grouping by gender, leave grouping by outcome
    +
    +

    NOTE: The verb count() automatically ungroups the data after counting.

    +
    +
    +

    13.4 Summarise

    +

    See the dplyr section of the Descriptive tables page for a detailed description of how to produce summary tables with summarise(). Here we briefly address how its behavior changes when applied to grouped data.

    +

    The dplyr function summarise() (or summarize()) takes a data frame and converts it into a new summary data frame, with columns containing summary statistics that you define. On an ungrouped data frame, the summary statistics will be calculated from all rows. Applying summarise() to grouped data produces those summary statistics for each group.

    +

    The syntax of summarise() is such that you provide the name(s) of the new summary column(s), an equals sign, and then a statistical function to apply to the data, as shown below. For example, min(), max(), median(), or sd(). Within the statistical function, list the column to be operated on and any relevant argument (e.g. na.rm = TRUE). You can use sum() to count the number of rows that meet a logical criteria (with double equals ==).

    +

    Below is an example of summarise() applied without grouped data. The statistics returned are produced from the entire dataset.

    +
    +
    # summary statistics on ungrouped linelist
    +linelist %>% 
    +  summarise(
    +    n_cases  = n(),
    +    mean_age = mean(age_years, na.rm=T),
    +    max_age  = max(age_years, na.rm=T),
    +    min_age  = min(age_years, na.rm=T),
    +    n_males  = sum(gender == "m", na.rm=T))
    +
    +
      n_cases mean_age max_age min_age n_males
    +1    5888 16.01831      84       0    2803
    +
    +
    +

    In contrast, below is the same summarise() statement applied to grouped data. The statistics are calculated for each outcome group. Note how grouping columns will carry over into the new data frame.

    +
    +
    # summary statistics on grouped linelist
    +linelist %>% 
    +  group_by(outcome) %>% 
    +  summarise(
    +    n_cases  = n(),
    +    mean_age = mean(age_years, na.rm=T),
    +    max_age  = max(age_years, na.rm=T),
    +    min_age  = min(age_years, na.rm=T),
    +    n_males    = sum(gender == "m", na.rm=T))
    +
    +
    # A tibble: 3 × 6
    +  outcome n_cases mean_age max_age min_age n_males
    +  <chr>     <int>    <dbl>   <dbl>   <dbl>   <int>
    +1 Death      2582     15.9      76       0    1228
    +2 Recover    1983     16.1      84       0     950
    +3 <NA>       1323     16.2      69       0     625
    +
    +
    +

    TIP: The summarise function works with both UK and US spelling - summarise() and summarize() call the same function.

    +
    +
    +

    13.5 Counts and tallies

    +

    count() and tally() provide similar functionality but are different. Read more about the distinction between tally() and count() here.

    +
    +

    tally()

    +

    tally() is shorthand for summarise(n = n()), and does not group data. Thus, to achieve grouped tallys it must follow a group_by() command. You can add sort = TRUE to see the largest groups first.

    +
    +
    linelist %>% 
    +  tally()
    +
    +
         n
    +1 5888
    +
    +
    +
    +
    linelist %>% 
    +  group_by(outcome) %>% 
    +  tally(sort = TRUE)
    +
    +
    # A tibble: 3 × 2
    +  outcome     n
    +  <chr>   <int>
    +1 Death    2582
    +2 Recover  1983
    +3 <NA>     1323
    +
    +
    +
    +
    +

    count()

    +

    In contrast, count() does the following:

    +
      +
    1. applies group_by() on the specified column(s).
      +
    2. +
    3. applies summarise() and returns column n with the number of rows per group.
      +
    4. +
    5. applies ungroup().
    6. +
    +
    +
    linelist %>% 
    +  count(outcome)
    +
    +
      outcome    n
    +1   Death 2582
    +2 Recover 1983
    +3    <NA> 1323
    +
    +
    +

    Just like with group_by() you can create a new column within the count() command:

    +
    +
    linelist %>% 
    +  count(age_class = ifelse(age >= 18, "adult", "child"), sort = T)
    +
    +
      age_class    n
    +1     child 3618
    +2     adult 2184
    +3      <NA>   86
    +
    +
    +

    count() can be called multiple times, with the functionality “rolling up”. For example, to summarise the number of hospitals present for each gender, run the following. Note, the name of the final column is changed from default “n” for clarity (with name =).

    +
    +
    linelist %>% 
    +  # produce counts by unique outcome-gender groups
    +  count(gender, hospital) %>% 
    +  # gather rows by gender (3) and count number of hospitals per gender (6)
    +  count(gender, name = "hospitals per gender" ) 
    +
    +
      gender hospitals per gender
    +1      f                    6
    +2      m                    6
    +3   <NA>                    6
    +
    +
    +
    +
    +

    Add counts

    +

    In contrast to count() and summarise(), you can use add_count() to add a new column n with the counts of rows per group while retaining all the other data frame columns.

    +

    This means that a group’s count number, in the new column n, will be printed in each row of the group. For demonstration purposes, we add this column and then re-arrange the columns for easier viewing. See the section below on filter on group size for another example.

    +
    +
    linelist %>% 
    +  as_tibble() %>%                   # convert to tibble for nicer printing 
    +  add_count(hospital) %>%           # add column n with counts by hospital
    +  select(hospital, n, everything()) # re-arrange for demo purposes
    +
    +
    # A tibble: 5,888 × 31
    +   hospital                       n case_id generation date_infection date_onset
    +   <chr>                      <int> <chr>        <dbl> <date>         <date>    
    + 1 Other                        885 5fe599           4 2014-05-08     2014-05-13
    + 2 Missing                     1469 8689b7           4 NA             2014-05-13
    + 3 St. Mark's Maternity Hosp…   422 11f8ea           2 NA             2014-05-16
    + 4 Port Hospital               1762 b8812a           3 2014-05-04     2014-05-18
    + 5 Military Hospital            896 893f25           3 2014-05-18     2014-05-21
    + 6 Port Hospital               1762 be99c8           3 2014-05-03     2014-05-22
    + 7 Missing                     1469 07e3e8           4 2014-05-22     2014-05-27
    + 8 Missing                     1469 369449           4 2014-05-28     2014-06-02
    + 9 Missing                     1469 f393b4           4 NA             2014-06-05
    +10 Missing                     1469 1389ca           4 NA             2014-06-05
    +# ℹ 5,878 more rows
    +# ℹ 25 more variables: date_hospitalisation <date>, date_outcome <date>,
    +#   outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,
    +#   age_cat <fct>, age_cat5 <fct>, lon <dbl>, lat <dbl>, infector <chr>,
    +#   source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>,
    +#   chills <chr>, cough <chr>, aches <chr>, vomit <chr>, temp <dbl>,
    +#   time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>
    +
    +
    +
    +
    +

    Add totals

    +

    To easily add total sum rows or columns after using tally() or count(), see the janitor section of the Descriptive tables page. This package offers functions like adorn_totals() and adorn_percentages() to add totals and convert to show percentages. Below is a brief example:

    +
    +
    linelist %>%                                  # case linelist
    +  tabyl(age_cat, gender) %>%                  # cross-tabulate counts of two columns
    +  adorn_totals(where = "row") %>%             # add a total row
    +  adorn_percentages(denominator = "col") %>%  # convert to proportions with column denominator
    +  adorn_pct_formatting() %>%                  # convert proportions to percents
    +  adorn_ns(position = "front") %>%            # display as: "count (percent)"
    +  adorn_title(                                # adjust titles
    +    row_name = "Age Category",
    +    col_name = "Gender")
    +
    +
                          Gender                            
    + Age Category              f              m          NA_
    +          0-4   640  (22.8%)   416  (14.8%)  39  (14.0%)
    +          5-9   641  (22.8%)   412  (14.7%)  42  (15.1%)
    +        10-14   518  (18.5%)   383  (13.7%)  40  (14.4%)
    +        15-19   359  (12.8%)   364  (13.0%)  20   (7.2%)
    +        20-29   468  (16.7%)   575  (20.5%)  30  (10.8%)
    +        30-49   179   (6.4%)   557  (19.9%)  18   (6.5%)
    +        50-69     2   (0.1%)    91   (3.2%)   2   (0.7%)
    +          70+     0   (0.0%)     5   (0.2%)   1   (0.4%)
    +         <NA>     0   (0.0%)     0   (0.0%)  86  (30.9%)
    +        Total 2,807 (100.0%) 2,803 (100.0%) 278 (100.0%)
    +
    +
    +

    To add more complex totals rows that involve summary statistics other than sums, see this section of the Descriptive Tables page.

    +
    +
    +
    +

    13.6 Grouping by date

    +

    When grouping data by date, you must have (or create) a column for the date unit of interest - for example “day”, “epiweek”, “month”, etc. You can make this column using floor_date() from lubridate, as explained in the Epidemiological weeks section of the Working with dates page. Once you have this column, you can use count() from dplyr to group the rows by those unique date values and achieve aggregate counts.

    +

    One additional step common for date situations, is to “fill-in” any dates in the sequence that are not present in the data. Use complete() from tidyr so that the aggregated date series is complete including all possible date units within the range. Without this step, a week with no cases reported might not appear in your data!

    +

    Within complete() you re-define your date column as a sequence of dates seq.Date() from the minimum to the maximum - thus the dates are expanded. By default, the case count values in any new “expanded” rows will be NA. You can set them to 0 using the fill = argument of complete(), which expects a named list (if your counts column is named n, provide fill = list(n = 0). See ?complete for details and the Working with dates page for an example.

    +
    +

    Linelist cases into days

    +

    Here is an example of grouping cases into days without using complete(). Note the first rows skip over dates with no cases.

    +
    +
    daily_counts <- linelist %>% 
    +  drop_na(date_onset) %>%        # remove that were missing date_onset
    +  count(date_onset)              # count number of rows per unique date
    +
    +
    +
    +
    + +
    +
    +

    Below we add the complete() command to ensure every day in the range is represented.

    +
    +
    daily_counts <- linelist %>% 
    +  drop_na(date_onset) %>%                 # remove case missing date_onset
    +  count(date_onset) %>%                   # count number of rows per unique date
    +  complete(                               # ensure all days appear even if no cases
    +    date_onset = seq.Date(                # re-define date colume as daily sequence of dates
    +      from = min(date_onset, na.rm=T), 
    +      to = max(date_onset, na.rm=T),
    +      by = "day"),
    +    fill = list(n = 0))                   # set new filled-in rows to display 0 in column n (not NA as default) 
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    Linelist cases into weeks

    +

    The same principle can be applied for weeks. First create a new column that is the week of the case using floor_date() with unit = "week". Then, use count() as above to achieve weekly case counts. Finish with complete() to ensure that all weeks are represented, even if they contain no cases.

    +
    +
    # Make dataset of weekly case counts
    +weekly_counts <- linelist %>% 
    +  drop_na(date_onset) %>%                 # remove cases missing date_onset
    +  mutate(week = lubridate::floor_date(date_onset, unit = "week")) %>%  # new column of week of onset
    +  count(week) %>%                         # group data by week and count rows per group
    +  complete(                               # ensure all days appear even if no cases
    +    week = seq.Date(                      # re-define date colume as daily sequence of dates
    +      from = min(week, na.rm=T), 
    +      to = max(week, na.rm=T),
    +      by = "week"),
    +    fill = list(n = 0))                   # set new filled-in rows to display 0 in column n (not NA as default) 
    +
    +

    Here are the first 50 rows of the resulting data frame:

    +
    +
    +
    + +
    +
    +
    +
    +

    Linelist cases into months

    +

    To aggregate cases into months, again use floor_date() from the lubridate package, but with the argument unit = "months". This rounds each date down to the 1st of its month. The output will be class Date. Note that in the complete() step we also use by = "months".

    +
    +
    # Make dataset of monthly case counts
    +monthly_counts <- linelist %>% 
    +  drop_na(date_onset) %>% 
    +  mutate(month = lubridate::floor_date(date_onset, unit = "months")) %>%  # new column, 1st of month of onset
    +  count(month) %>%                          # count cases by month
    +  complete(
    +    month = seq.Date(
    +      min(month, na.rm=T),     # include all months with no cases reported
    +      max(month, na.rm=T),
    +      by="month"),
    +    fill = list(n = 0))
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    Daily counts into weeks

    +

    To aggregate daily counts into weekly counts, use floor_date() as above. However, use group_by() and summarize() instead of count() because you need to sum() daily case counts instead of just counting the number of rows per week.

    +
    +

    Daily counts into months

    +

    To aggregate daily counts into months counts, use floor_date() with unit = "month" as above. However, use group_by() and summarize() instead of count() because you need to sum() daily case counts instead of just counting the number of rows per month.

    +
    +
    +
    +
    +

    13.7 Arranging grouped data

    +

    Using the dplyr verb arrange() to order the rows in a data frame behaves the same when the data are grouped, unless you set the argument .by_group =TRUE. In this case the rows are ordered first by the grouping columns and then by any other columns you specify to arrange().

    +
    +
    +

    13.8 Filter on grouped data

    +
    +

    filter()

    +

    When applied in conjunction with functions that evaluate the data frame (like max(), min(), mean()), these functions will now be applied to the groups. For example, if you want to filter and keep rows where patients are above the median age, this will now apply per group - filtering to keep rows above the group’s median age.

    +
    +
    +

    Slice rows per group

    +

    The dplyr function slice(), which filters rows based on their position in the data, can also be applied per group. Remember to account for sorting the data within each group to get the desired “slice”.

    +

    For example, to retrieve only the latest 5 admissions from each hospital:

    +
      +
    1. Group the linelist by column hospital.
      +
    2. +
    3. Arrange the records from latest to earliest date_hospitalisation within each hospital group.
      +
    4. +
    5. Slice to retrieve the first 5 rows from each hospital.
    6. +
    +
    +
    linelist %>%
    +  group_by(hospital) %>%
    +  arrange(hospital, date_hospitalisation) %>%
    +  slice_head(n = 5) %>% 
    +  arrange(hospital) %>%                            # for display
    +  select(case_id, hospital, date_hospitalisation)  # for display
    +
    +
    # A tibble: 30 × 3
    +# Groups:   hospital [6]
    +   case_id hospital          date_hospitalisation
    +   <chr>   <chr>             <date>              
    + 1 20b688  Central Hospital  2014-05-06          
    + 2 d58402  Central Hospital  2014-05-10          
    + 3 b8f2fd  Central Hospital  2014-05-13          
    + 4 acf422  Central Hospital  2014-05-28          
    + 5 275cc7  Central Hospital  2014-05-28          
    + 6 d1fafd  Military Hospital 2014-04-17          
    + 7 974bc1  Military Hospital 2014-05-13          
    + 8 6a9004  Military Hospital 2014-05-13          
    + 9 09e386  Military Hospital 2014-05-14          
    +10 865581  Military Hospital 2014-05-15          
    +# ℹ 20 more rows
    +
    +
    +

    slice_head() - selects n rows from the top.
    +slice_tail() - selects n rows from the end.
    +slice_sample() - randomly selects n rows.
    +slice_min() - selects n rows with highest values in order_by = column, use with_ties = TRUE to keep ties.
    +slice_max() - selects n rows with lowest values in order_by = column, use with_ties = TRUE to keep ties.

    +

    See the De-duplication page for more examples and detail on slice().

    +
    +
    +

    Filter on group size

    +

    The function add_count() adds a column n to the original data giving the number of rows in that row’s group.

    +

    Shown below, add_count() is applied to the column hospital, so the values in the new column n reflect the number of rows in that row’s hospital group. Note how values in column n are repeated. In the example below, the column name n could be changed using name = within add_count(). For demonstration purposes we re-arrange the columns with select().

    +
    +
    linelist %>% 
    +  as_tibble() %>% 
    +  add_count(hospital) %>%          # add "number of rows admitted to same hospital as this row" 
    +  select(hospital, n, everything())
    +
    +
    # A tibble: 5,888 × 31
    +   hospital                       n case_id generation date_infection date_onset
    +   <chr>                      <int> <chr>        <dbl> <date>         <date>    
    + 1 Other                        885 5fe599           4 2014-05-08     2014-05-13
    + 2 Missing                     1469 8689b7           4 NA             2014-05-13
    + 3 St. Mark's Maternity Hosp…   422 11f8ea           2 NA             2014-05-16
    + 4 Port Hospital               1762 b8812a           3 2014-05-04     2014-05-18
    + 5 Military Hospital            896 893f25           3 2014-05-18     2014-05-21
    + 6 Port Hospital               1762 be99c8           3 2014-05-03     2014-05-22
    + 7 Missing                     1469 07e3e8           4 2014-05-22     2014-05-27
    + 8 Missing                     1469 369449           4 2014-05-28     2014-06-02
    + 9 Missing                     1469 f393b4           4 NA             2014-06-05
    +10 Missing                     1469 1389ca           4 NA             2014-06-05
    +# ℹ 5,878 more rows
    +# ℹ 25 more variables: date_hospitalisation <date>, date_outcome <date>,
    +#   outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,
    +#   age_cat <fct>, age_cat5 <fct>, lon <dbl>, lat <dbl>, infector <chr>,
    +#   source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>, fever <chr>,
    +#   chills <chr>, cough <chr>, aches <chr>, vomit <chr>, temp <dbl>,
    +#   time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>
    +
    +
    +

    It then becomes easy to filter for case rows who were hospitalized at a “small” hospital, say, a hospital that admitted fewer than 500 patients:

    +
    +
    linelist %>% 
    +  add_count(hospital) %>% 
    +  filter(n < 500)
    +
    +
    +
    +
    +

    13.9 Mutate on grouped data

    +

    To retain all columns and rows (not summarise) and add a new column containing group statistics, use mutate() after group_by() instead of summarise().

    +

    This is useful if you want group statistics in the original dataset with all other columns present - e.g. for calculations that compare one row to its group.

    +

    For example, this code below calculates the difference between a row’s delay-to-admission and the median delay for their hospital. The steps are:

    +
      +
    1. Group the data by hospital.
      +
    2. +
    3. Use the column days_onset_hosp (delay to hospitalisation) to create a new column containing the mean delay at the hospital of that row.
      +
    4. +
    5. Calculate the difference between the two columns.
    6. +
    +

    We select() only certain columns to display, for demonstration purposes.

    +
    +
    linelist %>% 
    +  # group data by hospital (no change to linelist yet)
    +  group_by(hospital) %>% 
    +  
    +  # new columns
    +  mutate(
    +    # mean days to admission per hospital (rounded to 1 decimal)
    +    group_delay_admit = round(mean(days_onset_hosp, na.rm=T), 1),
    +    
    +    # difference between row's delay and mean delay at their hospital (rounded to 1 decimal)
    +    diff_to_group     = round(days_onset_hosp - group_delay_admit, 1)) %>%
    +  
    +  # select certain rows only - for demonstration/viewing purposes
    +  select(case_id, hospital, days_onset_hosp, group_delay_admit, diff_to_group)
    +
    +
    # A tibble: 5,888 × 5
    +# Groups:   hospital [6]
    +   case_id hospital              days_onset_hosp group_delay_admit diff_to_group
    +   <chr>   <chr>                           <dbl>             <dbl>         <dbl>
    + 1 5fe599  Other                               2               2             0  
    + 2 8689b7  Missing                             1               2.1          -1.1
    + 3 11f8ea  St. Mark's Maternity…               2               2.1          -0.1
    + 4 b8812a  Port Hospital                       2               2.1          -0.1
    + 5 893f25  Military Hospital                   1               2.1          -1.1
    + 6 be99c8  Port Hospital                       1               2.1          -1.1
    + 7 07e3e8  Missing                             2               2.1          -0.1
    + 8 369449  Missing                             1               2.1          -1.1
    + 9 f393b4  Missing                             1               2.1          -1.1
    +10 1389ca  Missing                             2               2.1          -0.1
    +# ℹ 5,878 more rows
    +
    +
    +
    +
    +

    13.10 Select on grouped data

    +

    The verb select() works on grouped data, but the grouping columns are always included (even if not mentioned in select()). If you do not want these grouping columns, use ungroup() first.

    + +
    +
    +

    13.11 Resources

    +

    Here are some useful resources for more information:

    +

    You can perform any summary function on grouped data; see the RStudio data transformation cheat sheet.

    +

    The Data Carpentry page on dplyr.
    +The tidyverse reference pages on group_by() and grouping.

    +

    This page on Data manipulation.

    +

    Summarize with conditions in dplyr.

    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/grouping.qmd b/new_pages/grouping.qmd index 90ab1f79..39e277f1 100644 --- a/new_pages/grouping.qmd +++ b/new_pages/grouping.qmd @@ -40,7 +40,8 @@ pacman::p_load( rio, # to import data here, # to locate files tidyverse, # to clean, handle, and plot the data (includes dplyr) - janitor) # adding total rows and columns + janitor # adding total rows and columns + ) ``` @@ -50,7 +51,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). The dataset is imported using the `import()` function from the **rio** package. See the page on [Import and export](importing.qmd) for various ways to import data. -```{r, echo=F} +```{r, echo=F, warning=F, message=F} linelist <- rio::import(here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -139,7 +140,7 @@ by_outcome_gender <- by_outcome %>% ``` -** Keep all groups** +**Keep all groups** If you group on a column of class factor there may be levels of the factor that are not currently present in the data. If you group on this column, by default those non-present levels are dropped and not included as groups. To change this so that all levels appear as groups (even if not present in the data), set `.drop = FALSE` in your `group_by()` command. @@ -211,7 +212,7 @@ linelist %>% ## Counts and tallies -`count()` and `tally()` provide similar functionality but are different. Read more about the distinction between `tally()` and `count()` [here](https://dplyr.tidyverse.org/reference/tally.html) +`count()` and `tally()` provide similar functionality but are different. Read more about the distinction between `tally()` and `count()` [here](https://dplyr.tidyverse.org/reference/count.html). ### `tally()` {.unnumbered} @@ -235,8 +236,8 @@ linelist %>% In contrast, `count()` does the following: 1) applies `group_by()` on the specified column(s) -2) applies `summarise()` and returns column `n` with the number of rows per group -3) applies `ungroup()` +2) applies `summarise()` and returns column `n` with the number of rows per group +3) applies `ungroup()` ```{r} linelist %>% @@ -424,9 +425,9 @@ The **dplyr** function `slice()`, which [filters rows based on their position](h For example, to retrieve only the latest 5 admissions from each hospital: -1) Group the linelist by column `hospital` -2) Arrange the records from latest to earliest `date_hospitalisation` *within each hospital group* -3) Slice to retrieve the first 5 rows from each hospital +1) Group the linelist by column `hospital` +2) Arrange the records from latest to earliest `date_hospitalisation` *within each hospital group* +3) Slice to retrieve the first 5 rows from each hospital ```{r,} linelist %>% @@ -482,9 +483,9 @@ This is useful if you want group statistics in the original dataset *with all ot For example, this code below calculates the difference between a row's delay-to-admission and the median delay for their hospital. The steps are: -1) Group the data by hospital -2) Use the column `days_onset_hosp` (delay to hospitalisation) to create a new column containing the mean delay at the hospital of *that row* -3) Calculate the difference between the two columns +1) Group the data by hospital +2) Use the column `days_onset_hosp` (delay to hospitalisation) to create a new column containing the mean delay at the hospital of *that row* +3) Calculate the difference between the two columns We `select()` only certain columns to display, for demonstration purposes. @@ -525,14 +526,14 @@ The verb `select()` works on grouped data, but the grouping columns are always i Here are some useful resources for more information: -You can perform any summary function on grouped data; see the [RStudio data transformation cheat sheet](https://github.com/rstudio/cheatsheets/blob/master/data-transformation.pdf) +You can perform any summary function on grouped data; see the [RStudio data transformation cheat sheet](https://github.com/rstudio/cheatsheets/blob/master/data-transformation.pdf). -The Data Carpentry page on [**dplyr**](https://datacarpentry.org/R-genomics/04-dplyr.html) -The **tidyverse** reference pages on [group_by()](https://dplyr.tidyverse.org/reference/group_by.html) and [grouping](https://dplyr.tidyverse.org/articles/grouping.html) +The Data Carpentry page on [**dplyr**](https://datacarpentry.org/R-genomics/04-dplyr.html). +The **tidyverse** reference pages on [group_by()](https://dplyr.tidyverse.org/reference/group_by.html) and [grouping](https://dplyr.tidyverse.org/articles/grouping.html). -This page on [Data manipulation](https://itsalocke.com/files/DataManipulationinR.pdf) +This page on [Data manipulation](https://itsalocke.com/files/DataManipulationinR.pdf). -[Summarize with conditions in dplyr](https://stackoverflow.com/questions/23528862/summarize-with-conditions-in-dplyr) +[Summarize with conditions in dplyr](https://stackoverflow.com/questions/23528862/summarize-with-conditions-in-dplyr). diff --git a/new_pages/heatmaps.qmd b/new_pages/heatmaps.qmd index 82cbcccd..b02fe307 100644 --- a/new_pages/heatmaps.qmd +++ b/new_pages/heatmaps.qmd @@ -4,8 +4,8 @@ Heat plots, also known as "heat maps" or "heat tiles", can be useful visualizations when trying to display 3 variables (x-axis, y-axis, and fill). Below we demonstrate two examples: -* A visual matrix of transmission events by age ("who infected whom") -* Tracking reporting metrics across many facilities/jurisdictions over time +* A visual matrix of transmission events by age ("who infected whom") +* Tracking reporting metrics across many facilities/jurisdictions over time ```{r, out.width = c('50%', '50%'), fig.show='hold', warning=F, message=F, echo=F} @@ -48,7 +48,7 @@ This page utilizes the case linelist of a simulated outbreak for the transmissio Heat tiles can be useful to visualize matrices. One example is to display "who-infected-whom" in an outbreak. This assumes that you have information on transmission events. -Note that the [Contact tracing] page contains another example of making a heat tile contact matrix, using a different (perhaps more simple) dataset where the ages of cases and their sources are neatly aligned in the same row of the data frame. This same data is used to make a *density* map in the [ggplot tips] page. This example below begins from a case linelist and so involves considerable data manipulation prior to achieving a plotable data frame. So there are many scenarios to chose from... +Note that the [Contact tracing](contact_tracing.qmd) page contains another example of making a heat tile contact matrix, using a different (perhaps more simple) dataset where the ages of cases and their sources are neatly aligned in the same row of the data frame. This same data is used to make a *density* map in the [ggplot tips](ggplot_tips.qmd) page. This example below begins from a case linelist and so involves considerable data manipulation prior to achieving a plottable data frame. So there are many scenarios to chose from. We begin from the case linelist of a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). @@ -57,7 +57,7 @@ We begin from the case linelist of a simulated Ebola epidemic. If you want to fo The first 50 rows of the linelist are shown below for demonstration: -```{r, echo=F} +```{r, echo=F, warning=F, message=F} linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -69,7 +69,7 @@ linelist <- import("linelist_cleaned.rds") In this linelist: -* There is one row per case, as identified by `case_id` +* There is one row per case, as identified by `case_id` * There is a later column `infector` that contains the `case_id` of the *infector*, who is also a case in the linelist @@ -194,22 +194,22 @@ DT::datatable(head(long_prop, 50), rownames = FALSE, options = list(pageLength = Now finally we can create the heat plot with **ggplot2** package, using the `geom_tile()` function. See the [ggplot tips](ggplot_tips.qmd) page to learn more extensively about color/fill scales, especially the `scale_fill_gradient()` function. -* In the aesthetics `aes()` of `geom_tile()` set the x and y as the case age and infector age -* Also in `aes()` set the argument `fill = ` to the `Freq` column - this is the value that will be converted to a tile color -* Set a scale color with `scale_fill_gradient()` - you can specify the high/low colors - * Note that `scale_color_gradient()` is different! In this case you want the fill -* Because the color is made via "fill", you can use the `fill = ` argument in `labs()` to change the legend title +* In the aesthetics `aes()` of `geom_tile()` set the x and y as the case age and infector age. +* Also in `aes()` set the argument `fill = ` to the `Freq` column - this is the value that will be converted to a tile color. +* Set a scale color with `scale_fill_gradient()` - you can specify the high/low colors. + * Note that `scale_color_gradient()` is different! In this case you want the fill. +* Because the color is made via "fill", you can use the `fill = ` argument in `labs()` to change the legend title. ```{r} -ggplot(data = long_prop)+ # use long data, with proportions as Freq +ggplot(data = long_prop) + # use long data, with proportions as Freq geom_tile( # visualize it in tiles aes( x = cases, # x-axis is case age y = infectors, # y-axis is infector age - fill = Freq))+ # color of the tile is the Freq column in the data + fill = Freq)) + # color of the tile is the Freq column in the data scale_fill_gradient( # adjust the fill color of the tiles low = "blue", - high = "orange")+ + high = "orange") + labs( # labels x = "Case age", y = "Infector age", @@ -233,7 +233,7 @@ Often in public health, one objective is to assess trends over time for many ent We begin by importing a dataset of daily malaria reports from many facilities. The reports contain a date, province, district, and malaria counts. See the page on [Download handbook and data](data_used.qmd) for information on how to download these data. Below are the first 30 rows: -```{r, echo=F} +```{r, echo=F, warning=F, message=F} facility_count_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% select(location_name, data_date, District, malaria_tot) ``` @@ -254,16 +254,16 @@ DT::datatable(head(facility_count_data,30), rownames = FALSE, options = list(pag To achieve this we will do the following data management steps: -1) Filter the data as appropriate (by place, date) -2) Create a week column using `floor_date()` from package **lubridate** - + This function returns the start-date of a given date's week, using a specified start date of each week (e.g. "Mondays") -3) The data are grouped by columns "location" and "week" to create analysis units of "facility-week" +1) Filter the data as appropriate (by place, date). +2) Create a week column using `floor_date()` from package **lubridate**. + + This function returns the start-date of a given date's week, using a specified start date of each week (e.g. "Mondays"). +3) The data are grouped by columns "location" and "week" to create analysis units of "facility-week". 4) The function `summarise()` creates new columns to reflecting summary statistics per facility-week group: - + Number of days per week (7 - a static value) - + Number of reports received from the facility-week (could be more than 7!) - + Sum of malaria cases reported by the facility-week (just for interest) - + Number of *unique* days in the facility-week for which there is data reported - + **Percent of the 7 days per facility-week for which data was reported** + + Number of days per week (7 - a static value) + + Number of reports received from the facility-week (could be more than 7!) + + Sum of malaria cases reported by the facility-week (just for interest) + + Number of *unique* days in the facility-week for which there is data reported + + **Percent of the 7 days per facility-week for which data was reported** 5) The data frame is joined with `right_join()` to a comprehensive list of all possible facility-week combinations, to make the dataset complete. The matrix of all possible combinations is created by applying `expand()` to those two columns of the data frame as it is at that moment in the pipe chain (represented by `.`). Because a `right_join()` is used, all rows in the `expand()` data frame are kept, and added to `agg_weeks` if necessary. These new rows appear with `NA` (missing) summarized values. @@ -272,7 +272,6 @@ Below we demonstrate step-by-step: ```{r, message=FALSE, warning=FALSE} # Create weekly summary dataset agg_weeks <- facility_count_data %>% - # filter the data as appropriate filter( District == "Spring", @@ -358,11 +357,11 @@ After running this code, `agg_weeks` contains ` nrow(agg_weeks)` rows. The `ggplot()` is made using `geom_tile()` from the **ggplot2** package: -* Weeks on the x-axis is transformed to dates, allowing use of `scale_x_date()` -* `location_name` on the y-axis will show all facility names -* The `fill` is `p_days_reported`, the performance for that facility-week (numeric) -* `scale_fill_gradient()` is used on the numeric fill, specifying colors for high, low, and `NA` -* `scale_x_date()` is used on the x-axis specifying labels every 2 weeks and their format +* Weeks on the x-axis is transformed to dates, allowing use of `scale_x_date()` +* `location_name` on the y-axis will show all facility names +* The `fill` is `p_days_reported`, the performance for that facility-week (numeric) +* `scale_fill_gradient()` is used on the numeric fill, specifying colors for high, low, and `NA` +* `scale_x_date()` is used on the x-axis specifying labels every 2 weeks and their format * Display themes and labels can be adjusted as necessary @@ -374,7 +373,7 @@ The `ggplot()` is made using `geom_tile()` from the **ggplot2** package: A basic heat plot is produced below, using the default colors, scales, etc. As explained above, within the `aes()` for `geom_tile()` you must provide an x-axis column, y-axis column, **and** a column for the the `fill = `. The fill is the numeric value that presents as tile color. ```{r} -ggplot(data = agg_weeks)+ +ggplot(data = agg_weeks) + geom_tile( aes(x = week, y = location_name, @@ -386,28 +385,28 @@ ggplot(data = agg_weeks)+ We can make this plot look better by adding additional **ggplot2** functions, as shown below. See the page on [ggplot tips](ggplot_tips.qmd) for details. ```{r, message=FALSE, warning=FALSE} -ggplot(data = agg_weeks)+ +ggplot(data = agg_weeks) + # show data as tiles geom_tile( aes(x = week, y = location_name, fill = p_days_reported), - color = "white")+ # white gridlines + color = "white") + # white gridlines scale_fill_gradient( low = "orange", high = "darkgreen", - na.value = "grey80")+ + na.value = "grey80") + # date axis scale_x_date( expand = c(0,0), # remove extra space on sides date_breaks = "2 weeks", # labels every 2 weeks - date_labels = "%d\n%b")+ # format is day over month (\n in newline) + date_labels = "%d\n%b") + # format is day over month (\n in newline) # aesthetic themes - theme_minimal()+ # simplify background + theme_minimal() + # simplify background theme( legend.title = element_text(size=12, face="bold"), @@ -422,7 +421,7 @@ ggplot(data = agg_weeks)+ plot.title = element_text(hjust=0,size=14,face="bold"), # title right-aligned, large, bold plot.caption = element_text(hjust = 0, face = "italic") # caption right-aligned and italic - )+ + ) + # plot labels labs(x = "Week", @@ -469,52 +468,58 @@ Now use a column from the above data frame (`facility_order$location_name`) to b pacman::p_load(forcats) # create factor and define levels manually +numerical_order <- gsub("Facility ", "", facility_order$location_name) %>% + as.numeric() %>% + sort() + +facilities_in_order <- str_c("Facility ", numerical_order, sep = "") + agg_weeks <- agg_weeks %>% mutate(location_name = fct_relevel( - location_name, facility_order$location_name) + location_name, facilities_in_order) ) ``` And now the data are re-plotted, with location_name being an ordered factor: ```{r, message=FALSE, warning=FALSE} -ggplot(data = agg_weeks)+ +ggplot(data = agg_weeks) + # show data as tiles geom_tile( aes(x = week, y = location_name, fill = p_days_reported), - color = "white")+ # white gridlines + color = "white") + # white gridlines scale_fill_gradient( low = "orange", high = "darkgreen", - na.value = "grey80")+ + na.value = "grey80") + # date axis scale_x_date( expand = c(0,0), # remove extra space on sides date_breaks = "2 weeks", # labels every 2 weeks - date_labels = "%d\n%b")+ # format is day over month (\n in newline) + date_labels = "%d\n%b") + # format is day over month (\n in newline) # aesthetic themes - theme_minimal()+ # simplify background + theme_minimal() + # simplify background theme( - legend.title = element_text(size=12, face="bold"), - legend.text = element_text(size=10, face="bold"), + legend.title = element_text(size = 12, face = "bold"), + legend.text = element_text(size = 10, face = "bold"), legend.key.height = grid::unit(1,"cm"), # height of legend key legend.key.width = grid::unit(0.6,"cm"), # width of legend key - axis.text.x = element_text(size=12), # axis text size - axis.text.y = element_text(vjust=0.2), # axis text alignment - axis.ticks = element_line(size=0.4), - axis.title = element_text(size=12, face="bold"), # axis title size and bold + axis.text.x = element_text(size = 12), # axis text size + axis.text.y = element_text(vjust = 0.2), # axis text alignment + axis.ticks = element_line(size = 0.4), + axis.title = element_text(size = 12, face = "bold"), # axis title size and bold - plot.title = element_text(hjust=0,size=14,face="bold"), # title right-aligned, large, bold + plot.title = element_text(hjust = 0, size = 14, face = "bold"), # title right-aligned, large, bold plot.caption = element_text(hjust = 0, face = "italic") # caption right-aligned and italic - )+ + ) + # plot labels labs(x = "Week", @@ -541,51 +546,51 @@ The following code has been added: `geom_text(aes(label = p_days_reported))`. Th ```{r, message=FALSE, warning=FALSE} -ggplot(data = agg_weeks)+ +ggplot(data = agg_weeks) + # show data as tiles geom_tile( aes(x = week, y = location_name, fill = p_days_reported), - color = "white")+ # white gridlines + color = "white") + # white gridlines # text geom_text( aes( x = week, y = location_name, - label = p_days_reported))+ # add text on top of tile + label = p_days_reported)) + # add text on top of tile # fill scale scale_fill_gradient( low = "orange", high = "darkgreen", - na.value = "grey80")+ + na.value = "grey80") + # date axis scale_x_date( expand = c(0,0), # remove extra space on sides date_breaks = "2 weeks", # labels every 2 weeks - date_labels = "%d\n%b")+ # format is day over month (\n in newline) + date_labels = "%d\n%b") + # format is day over month (\n in newline) # aesthetic themes - theme_minimal()+ # simplify background + theme_minimal() + # simplify background theme( - legend.title = element_text(size=12, face="bold"), - legend.text = element_text(size=10, face="bold"), + legend.title = element_text(size = 12, face = "bold"), + legend.text = element_text(size = 10, face = "bold"), legend.key.height = grid::unit(1,"cm"), # height of legend key legend.key.width = grid::unit(0.6,"cm"), # width of legend key - axis.text.x = element_text(size=12), # axis text size - axis.text.y = element_text(vjust=0.2), # axis text alignment - axis.ticks = element_line(size=0.4), - axis.title = element_text(size=12, face="bold"), # axis title size and bold + axis.text.x = element_text(size = 12), # axis text size + axis.text.y = element_text(vjust = 0.2), # axis text alignment + axis.ticks = element_line(size = 0.4), + axis.title = element_text(size = 12, face = "bold"), # axis title size and bold - plot.title = element_text(hjust=0,size=14,face="bold"), # title right-aligned, large, bold + plot.title = element_text(hjust = 0,size = 14,face = "bold"), # title right-aligned, large, bold plot.caption = element_text(hjust = 0, face = "italic") # caption right-aligned and italic - )+ + ) + # plot labels labs(x = "Week", @@ -604,7 +609,7 @@ ggplot(data = agg_weeks)+ [scale_fill_gradient()](https://ggplot2.tidyverse.org/reference/scale_gradient.html) -[R graph gallery - heatmap](https://ggplot2.tidyverse.org/reference/scale_gradient.html) +[R graph gallery - heatmap](https://r-graph-gallery.com/heatmap) diff --git a/new_pages/help.qmd b/new_pages/help.qmd index 5eaa5835..f1dfafbf 100644 --- a/new_pages/help.qmd +++ b/new_pages/help.qmd @@ -3,7 +3,9 @@ This page covers how to get help by posting a Github issue or by posting a reproducible example ("reprex") to an online forum. - +```{r, echo = F, warning = F, message = F} +library(tidyverse) +``` ## Github issues @@ -39,7 +41,7 @@ To read more advanced materials about handling issues in your own Github reposit Providing a reproducible example ("reprex") is key to getting help when posting in a forum or in a Github issue. People want to help you, but you have to give them an example that they can work with on their own computer. The example should: * Demonstrate the problem you encountered -* Be *minimal*, in that it includes only the data and code required to reproduce your problem +* Be *minimal*, in that it includes only the data and code required to reproduce your problem * Be *reproducible*, such that all objects (e.g. data), package calls (e.g. `library()` or `p_load()`) are included *Also, be sure you do not post any sensitive data with the reprex!* You can create example data frames, or use one of the data frames built into R (enter `data()` to open a list of these datasets). @@ -50,7 +52,7 @@ Providing a reproducible example ("reprex") is key to getting help when posting The **reprex** package can assist you with making a reproducible example: -1) **reprex** is installed with **tidyverse**, so load either package +1) **reprex** is installed with **tidyverse**, so load either package. ```{r, eval=F} # install/load tidyverse (which includes reprex) @@ -63,7 +65,8 @@ pacman::p_load(tidyverse) # load packages pacman::p_load( tidyverse, # data mgmt and vizualization - outbreaks) # example outbreak datasets + outbreaks # example outbreak datasets + ) # flu epidemic case linelist outbreak_raw <- outbreaks::fluH7N9_china_2013 # retrieve dataset from outbreaks package @@ -74,13 +77,13 @@ outbreak <- outbreak_raw %>% # Plot epidemic -ggplot(data = outbreak)+ +ggplot(data = outbreak) + geom_histogram( mapping = aes(x = date_of_onset), binwidth = 7 - )+ + ) + scale_x_date( - date_format = "%d %m" + date_labels = "%d %m" ) ``` @@ -97,27 +100,75 @@ knitr::include_graphics(here::here("images", "errors_reprex_RStudio1.png")) ``` -* If you set `session_info = TRUE` the output of `sessioninfo::session_info()` with your R and R package versions will be included -* You can provide a working directory to `wd = ` -* You can read more about the arguments and possible variations at the [documentation]() or by entering `?reprex` +* If you set `session_info = TRUE` the output of `sessioninfo::session_info()` with your R and R package versions will be included +* You can provide a working directory to `wd = ` +* You can read more about the arguments and possible variations at the [documentation](https://reprex.tidyverse.org/) or by entering `?reprex` -In the example above, the `ggplot()` command did not run because the arguemnt `date_format =` is not correct - it should be `date_labels = `. +In the example above, the `ggplot()` command did not run because the argument `date_format =` is not correct - it should be `date_labels = `. ### Minimal data {.unnumbered} -The helpers need to be able to use your data - ideally they need to be able to create it *with code*. +The helpers need to be able to use your data - ideally they need to be able to create it *with code*. To create a minimal dataset, consider anonymising and using only a subset of the observations, or replicating the data using "dummy values". These seek to replicate the style (same class of variables, same amount of data, etc), but do not contain any sensitive information. + +For example, imagine we have a linelist of those infected with a novel pathogen. + +```{r} + +#Create dataset +linelist_data <- data.frame( + first_name = c("John", "Jane", "Joe", "Tom", "Richard", "Harry"), + last_name = c("Doe", "Doe", "Bloggs", "Jerry", "Springer", "Potter"), + age_years = c(25, 34, 27, 89, 52, 47), + location = as.factor(c("London", "Paris", "Berlin", "Tokyo", "Canberra", "Rio de Janeiro")), + outcome = c("Died", "Recovered", NA, "Recovered", "Recovered", "Died"), + symptoms = as.factor(c(T, F, F, T, T, F)) +) -To create a minumal dataset, consider anonymising and using only a subset of the observations. +``` + +You can imagine that this would be very sensitive information, and it would not be appropriate (or even legal!) to share this information. To anonymise the data you could think about removing columns that are not necessary for the analysis, but are identifying. For instance you could: + +* Replace `first_name` and `last_name` with an `id` column, that uses the rownumber +* Recode the `location` column so that it is not apparent where they were located +* Change the outcome to a binary value, to avoid disclosing their condition -UNDER CONSTRUCTION - you can also use the function `dput()` to create minimal dataset. +We will be using the inbuilt vector `LETTERS`. This is the alphabet in capital letters, and is automatically part of your environment when you open R. +```{r} +linelist_data %>% + mutate(id = row_number(), + location = as.factor(LETTERS[as.numeric(as.factor(location))]), + outcome = as.character(as.factor(outcome))) %>% + select(id, age_years, location, outcome, symptoms) + + +``` + +You could also generate new data, where columns are the same class (`class()`). This would also produce a reproducible dataset that does not contain any sensitive information. + +```{r} + +dummy_dataset <- data.frame( + first_name = LETTERS[1:6], + last_name = unique(iris$Species), + age_years = sample(1:100, 6, replace = T), + location = sample(row.names(USArrests), 6, replace = T), + outcome = sample(c("Died", "Recovered"), 6, replace = T), + symptoms = sample(c(T, F), 6, replace = T) + ) + +dummy_dataset + +``` ## Posting to a forum -Read lots of forum posts. Get an understanding for which posts are well-written, and which ones are not. +One highly recommend forum for posting your questions on is the [Applied Epi Community Forum](https://community.appliedepi.org/). There are a wealth of topics, and experts, to be found on our forum, ready to help you with questions on Epi methods, R code, and many other topics. *Note, you will have to make an account in order to post a question!* + +In order to make it as easy as possible for others to understand and help you, you should approach posting your question as follows. 1) First, decide whether to ask the question at all. Have you *thoroughly* reviewed the forum website, trying various search terms, to see if your question has already been asked? @@ -125,18 +176,18 @@ Read lots of forum posts. Get an understanding for which posts are well-written, 3) Write your question: -* Introduce your situation and problem +* Introduce your situation and problem * Link to posts of similar issues and explain how they do not answer your question -* Include any relevant information to help someone who does not know the context of your work -* Give a minimal reproducible example with your R session information -* Use proper spelling, grammar, punctuation, and break your question into paragraphs so that it is easier to read +* Include any relevant information to help someone who does not know the context of your work +* Give a minimal reproducible example with your R session information +* Use proper spelling, grammar, punctuation, and break your question into paragraphs so that it is easier to read -4) Monitor your question once posted to respond to any requests for clarification. Be courteous and gracious - often the people answering are volunteering their time to help you. If you have a follow-up question consider whether it should be a separate posted question. +4) Monitor your question once posted to respond to any requests for clarification. Be courteous and gracious - often the people answering are volunteering their time to help you. If you have a follow-up question consider whether it should be a separate posted question -5) Mark the question as answered, *if* you get an answer that meets the *original* request. This helps others later quickly recognize the solution. +5) Mark the question as answered, *if* you get an answer that meets the *original* request. This helps others later quickly recognize the solution -Read these posts about [how to ask a good question](https://stackoverflow.com/help/how-to-ask) the [Stack overflow code of conduct](https://stackoverflow.com/conduct). +Read these posts about [how to ask a good question](https://stackoverflow.com/help/how-to-ask) the [Stack overflow code of conduct](https://stackoverflow.com/conduct), and the [Applied Epi Community post on "How to make a reproducible R code example"](https://community.appliedepi.org/t/how-to-make-a-reproducible-r-code-example/167). @@ -147,4 +198,6 @@ Tidyverse page on how to [get help!](https://www.tidyverse.org/help/#:~:text=Whe Tips on [producing a minimal dataset](https://xiangxing98.github.io/R_Learning/R_Reproducible.nb.html#producing-a-minimal-dataset) -Documentation for the [dput function](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/dput) +The [Applied Epi Community forum](https://community.appliedepi.org/) + +[Applied Epi Community post on "How to make a reproducible R code example"](https://community.appliedepi.org/t/how-to-make-a-reproducible-r-code-example/167). diff --git a/new_pages/importing.html b/new_pages/importing.html new file mode 100644 index 00000000..b0de66ec --- /dev/null +++ b/new_pages/importing.html @@ -0,0 +1,2405 @@ + + + + + + + + + +The Epidemiologist R Handbook - 7  Import and export + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    7  Import and export

    +
    + + + +
    + + + + +
    + + + +
    + + +
    +
    +
    +
    +

    +
    +
    +
    +
    +

    In this page we describe ways to locate, import, and export files:

    +
      +
    • Use of the rio package to flexibly import() and export() many types of files.
      +
    • +
    • Use of the here package to locate files relative to an R project root. To prevent complications from file paths that are specific to one computer.
      +
    • +
    • Specific import scenarios, such as: +
        +
      • Specific Excel sheets.
        +
      • +
      • Messy headers and skipping rows.
        +
      • +
      • From Google sheets.
        +
      • +
      • From data posted to websites.
        +
      • +
      • With APIs.
        +
      • +
      • Importing the most recent file.
        +
      • +
    • +
    • Manual data entry.
      +
    • +
    • R-specific file types such as RDS and RData.
      +
    • +
    • Exporting/saving files and plots.
    • +
    + +
    +

    7.1 Overview

    +

    When you import a “dataset” into R, you are generally creating a new data frame object in your R environment and defining it as an imported file (e.g. Excel, CSV, TSV, RDS) that is located in your folder directories at a certain file path/address.

    +

    You can import/export many types of files, including those created by other statistical programs (SAS, STATA, SPSS). You can also connect to relational databases.

    +

    R even has its own data formats:

    +
      +
    • An RDS file (.rds) stores a single R object such as a data frame. These are useful to store cleaned data, as they maintain R column classes. Read more in this section.
      +
    • +
    • An RData file (.Rdata) can be used to store multiple objects, or even a complete R workspace. Read more in this section.
    • +
    + +
    +
    +

    7.2 The rio package

    +

    The R package we recommend is: rio. The name “rio” is an abbreviation of “R I/O” (input/output).

    +

    Its functions import() and export() can handle many different file types (e.g. .xlsx, .csv, .rds, .tsv). When you provide a file path to either of these functions (including the file extension like “.csv”), rio will read the extension and use the correct tool to import or export the file.

    +

    The alternative to using rio is to use functions from many other packages, each of which is specific to a type of file. For example, read.csv() (base R), read.xlsx() (openxlsx package), and write_csv() (readr pacakge), etc. These alternatives can be difficult to remember, whereas using import() and export() from rio is easy.

    +

    rio’s functions import() and export() use the appropriate package and function for a given file, based on its file extension. See the end of this page for a complete table of which packages/functions rio uses in the background. It can also be used to import STATA, SAS, and SPSS files, among dozens of other file types.

    +

    Import/export of shapefiles requires other packages, as detailed in the page on GIS basics.

    +
    +
    +

    7.3 The here package

    +

    The package here and its function here() make it easy to tell R where to find and to save your files - in essence, it builds file paths.

    +

    Used in conjunction with an R project, here allows you to describe the location of files in your R project in relation to the R project’s root directory (the top-level folder). This is useful when the R project may be shared or accessed by multiple people/computers. It prevents complications due to the unique file paths on different computers (e.g. "C:/Users/Laura/Documents..." by “starting” the file path in a place common to all users (the R project root).

    +

    This is how here() works within an R project:

    +
      +
    • When the here package is first loaded within the R project, it places a small file called “.here” in the root folder of your R project as a “benchmark” or “anchor”.
      +
    • +
    • In your scripts, to reference a file in the R project’s sub-folders, you use the function here() to build the file path in relation to that anchor.
    • +
    • To build the file path, write the names of folders beyond the root, within quotes, separated by commas, finally ending with the file name and file extension as shown below.
      +
    • +
    • here() file paths can be used for both importing and exporting.
    • +
    +

    For example, below, the function import() is being provided a file path constructed with here().

    +
    +
    linelist <- import(here("data", "linelists", "ebola_linelist.xlsx"))
    +
    +

    The command here("data", "linelists", "ebola_linelist.xlsx") is actually providing the full file path that is unique to the user’s computer:

    +
    "C:/Users/Laura/Documents/my_R_project/data/linelists/ebola_linelist.xlsx"
    +

    The beauty is that the R command using here() can be successfully run on any computer accessing the R project.

    +

    TIP: If you are unsure where the “.here” root is set to, run the function here() with empty parentheses.

    +

    Read more about the here package at this link.

    + +
    +
    +

    7.4 File paths

    +

    When importing or exporting data, you must provide a file path. You can do this one of three ways:

    +
      +
    1. Recommended: provide a “relative” file path with the here package.
      +
    2. +
    3. Provide the “full” / “absolute” file path.
      +
    4. +
    5. Manual file selection.
    6. +
    +
    +

    “Relative” file paths

    +

    In R, “relative” file paths consist of the file path relative to the root of an R project. They allow for more simple file paths that can work on different computers (e.g. if the R project is on a shared drive or is sent by email). As described above, relative file paths are facilitated by use of the here package.

    +

    An example of a relative file path constructed with here() is below. We assume the work is in an R project that contains a sub-folder “data” and within that a subfolder “linelists”, in which there is the .xlsx file of interest.

    +
    +
    linelist <- import(here("data", "linelists", "ebola_linelist.xlsx"))
    +
    +
    +
    +

    “Absolute” file paths

    +

    Absolute or “full” file paths can be provided to functions like import() but they are “fragile” as they are unique to the user’s specific computer and therefore not recommended.

    +

    Below is an example of an absolute file path, where in Laura’s computer there is a folder “analysis”, a sub-folder “data” and within that a sub-folder “linelists”, in which there is the .xlsx file of interest.

    +
    +
    linelist <- import("C:/Users/Laura/Documents/analysis/data/linelists/ebola_linelist.xlsx")
    +
    +

    A few things to note about absolute file paths:

    +
      +
    • Avoid using absolute file paths as they will not work if the script is run on a different computer.
    • +
    • Use forward slashes (/), as in the example above (note: this is NOT the default for Windows file paths).
      +
    • +
    • File paths that begin with double slashes (e.g. “//…”) will likely not be recognized by R and will produce an error. Consider moving your work to a “named” or “lettered” drive that begins with a letter (e.g. “J:” or “C:”). See the page on Directory interactions for more details on this issue.
    • +
    +

    One scenario where absolute file paths may be appropriate is when you want to import a file from a shared drive that has the same full file path for all users.

    +

    TIP: To quickly convert all \ to /, highlight the code of interest, use Ctrl+f (in Windows), check the option box for “In selection”, and then use the replace functionality to convert them.

    + +
    +
    +

    Select file manually

    +

    You can import data manually via one of these methods:

    +
      +
    1. Environment RStudio Pane, click “Import Dataset”, and select the type of data.
    2. +
    3. Click File / Import Dataset / (select the type of data).
      +
    4. +
    5. To hard-code manual selection, use the base R command file.choose() (leaving the parentheses empty) to trigger appearance of a pop-up window that allows the user to manually select the file from their computer. For example:
    6. +
    +
    +
    # Manual selection of a file. When this command is run, a POP-UP window will appear. 
    +# The file path selected will be supplied to the import() command.
    +
    +my_data <- import(file.choose())
    +
    +

    TIP: The pop-up window may appear BEHIND your RStudio window.

    +
    +
    +
    +

    7.5 Import data

    +

    To use import() to import a dataset is quite simple. Simply provide the path to the file (including the file name and file extension) in quotes. If using here() to build the file path, follow the instructions above. Below are a few examples:

    +

    Importing a csv file that is located in your “working directory” or in the R project root folder:

    +
    +
    linelist <- import("linelist_cleaned.csv")
    +
    +

    Importing the first sheet of an Excel workbook that is located in “data” and “linelists” sub-folders of the R project (the file path built using here()):

    +
    +
    linelist <- import(here("data", "linelists", "linelist_cleaned.xlsx"))
    +
    +

    Importing a data frame (a .rds file) using an absolute file path:

    +
    +
    linelist <- import("C:/Users/Laura/Documents/tuberculosis/data/linelists/linelist_cleaned.rds")
    +
    +
    +

    Specific Excel sheets

    +

    By default, if you provide an Excel workbook (.xlsx) to import(), the workbook’s first sheet will be imported. If you want to import a specific sheet, include the sheet name to the which = argument. For example:

    +
    +
    my_data <- import("my_excel_file.xlsx", which = "Sheetname")
    +
    +

    If using the here() method to provide a relative pathway to import(), you can still indicate a specific sheet by adding the which = argument after the closing parentheses of the here() function.

    +
    +
    # Demonstration: importing a specific Excel sheet when using relative pathways with the 'here' package
    +linelist_raw <- import(here("data", "linelist.xlsx"), which = "Sheet1")`  
    +
    +

    To export a data frame from R to a specific Excel sheet and have the rest of the Excel workbook remain unchanged, you will have to import, edit, and export with an alternative package catered to this purpose such as openxlsx. See more information in the page on Directory interactions or at this github page.

    +

    If your Excel workbook is .xlsb (binary format Excel workbook) you may not be able to import it using rio. Consider re-saving it as .xlsx, or using a package like readxlsb which is built for this purpose.

    + +
    +
    +

    Missing values

    +

    You may want to designate which value(s) in your dataset should be considered as missing. As explained in the page on Missing data, the value in R for missing data is NA, but perhaps the dataset you want to import uses 99, “Missing”, or just empty character space “” instead.

    +

    Use the na = argument for import() and provide the value(s) within quotes (even if they are numbers). You can specify multiple values by including them within a vector, using c() as shown below.

    +

    Here, the value “99” in the imported dataset is considered missing and converted to NA in R.

    +
    +
    linelist <- import(here("data", "my_linelist.xlsx"), na = "99")
    +
    +

    Here, any of the values “Missing”, “” (empty cell), or ” ” (single space) in the imported dataset are converted to NA in R.

    +
    +
    linelist <- import(here("data", "my_linelist.csv"), na = c("Missing", "", " "))
    +
    + +
    +
    +

    Skip rows

    +

    Sometimes, you may want to avoid importing a row of data. You can do this with the argument skip = if using import() from rio on a .xlsx or .csv file. Provide the number of rows you want to skip.

    +
    +
    linelist_raw <- import("linelist_raw.xlsx", skip = 1)  # does not import header row
    +
    +

    Unfortunately skip = only accepts one integer value, not a range (e.g. “2:10” does not work). To skip import of specific rows that are not consecutive from the top, consider importing multiple times and using bind_rows() from dplyr. See the example below of skipping only row 2.

    +
    +
    +

    Manage a second header row

    +

    Sometimes, your data may have a second row, for example if it is a “data dictionary” row as shown below. This situation can be problematic because it can result in all columns being imported as class “character”.

    +
    +
    +
    Warning: The `trust` argument of `import()` should be explicit for serialization formats
    +as of rio 1.0.3.
    +ℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.
    +ℹ The deprecated feature was likely used in the rio package.
    +  Please report the issue at <https://github.com/gesistsa/rio/issues>.
    +
    +
    +

    Below is an example of this kind of dataset (with the first row being the data dictionary).

    +
    +
    +
    + +
    +
    +
    +

    Remove the second header row

    +

    To drop the second header row, you will likely need to import the data twice.

    +
      +
    1. Import the data in order to store the correct column names
      +
    2. +
    3. Import the data again, skipping the first two rows (header and second rows)
      +
    4. +
    5. Bind the correct names onto the reduced dataframe
    6. +
    +

    The exact argument used to bind the correct column names depends on the type of data file (.csv, .tsv, .xlsx, etc.). This is because rio is using a different function for the different file types (see table above).

    +

    For Excel files: (col_names =)

    +
    +
    # import first time; store the column names
    +linelist_raw_names <- import("linelist_raw.xlsx") %>% 
    +     names()  # save true column names
    +
    +# import second time; skip row 2, and assign column names to argument col_names =
    +linelist_raw <- import("linelist_raw.xlsx",
    +                       skip = 2,
    +                       col_names = linelist_raw_names
    +                       ) 
    +
    +

    For CSV files: (col.names =)

    +
    +
    # import first time; sotre column names
    +linelist_raw_names <- import("linelist_raw.csv") %>% 
    +     names() # save true column names
    +
    +# note argument for csv files is 'col.names = '
    +linelist_raw <- import("linelist_raw.csv",
    +                       skip = 2,
    +                       col.names = linelist_raw_names
    +                       ) 
    +
    +

    Backup option - changing column names as a separate command

    +
    +
    # assign/overwrite headers using the base 'colnames()' function
    +colnames(linelist_raw) <- linelist_raw_names
    +
    +
    +
    +

    Make a data dictionary

    +

    Bonus! If you do have a second row that is a data dictionary, you can easily create a proper data dictionary from it. This tip is adapted from this post.

    +
    +
    dict <- linelist_2headers %>%             # begin: linelist with dictionary as first row
    +  head(1) %>%                             # keep only column names and first dictionary row                
    +  pivot_longer(cols = everything(),       # pivot all columns to long format
    +               names_to = "Column",       # assign new column names
    +               values_to = "Description")
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    Combine the two header rows

    +

    In some cases when your raw dataset has two header rows (or more specifically, the 2nd row of data is a secondary header), you may want to “combine” them or add the values in the second header row into the first header row.

    +

    The command below will define the data frame’s column names as the combination (pasting together) of the first (true) headers with the value immediately underneath (in the first row).

    +
    +
    names(my_data) <- paste(names(my_data), my_data[1, ], sep = "_")
    +
    + +
    +
    +
    +

    Google sheets

    +

    You can import data from an online Google spreadsheet with the googlesheet4 package and by authenticating your access to the spreadsheet.

    +
    +
    pacman::p_load("googlesheets4")
    +
    +

    Below, a demo Google sheet is imported and saved. This command may prompt confirmation of authentification of your Google account. Follow prompts and pop-ups in your internet browser to grant Tidyverse API packages permissions to edit, create, and delete your spreadsheets in Google Drive.

    +

    The sheet below is “viewable for anyone with the link” and you can try to import it.

    +
    +
    Gsheets_demo <- read_sheet("https://docs.google.com/spreadsheets/d/1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY/edit#gid=0")
    +
    +

    The sheet can also be imported using only the sheet ID, a shorter part of the URL:

    +
    +
    Gsheets_demo <- read_sheet("1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY")
    +
    +

    Another package, googledrive offers useful functions for writing, editing, and deleting Google sheets. For example, using the gs4_create() and sheet_write() functions found in this package.

    +

    Here are some other helpful online tutorials:
    +Google sheets importing tutorial. More detailed tutorial.
    +Interaction between the googlesheets4 and tidyverse.

    +

    Additionally, you can also use import from the rio package.

    +
    +
    Gsheets_demo <- rio("https://docs.google.com/spreadsheets/d/1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY/edit#gid=0")
    +
    +
    +
    +
    +

    7.6 Multiple files - import, export, split, combine

    +

    See the page on Iteration, loops, and lists for examples of how to import and combine multiple files, or multiple Excel workbook files.

    +

    That page also has examples on how to split a data frame into parts and export each one separately, or as named sheets in an Excel workbook.

    + +
    +
    +

    7.7 Import from Github

    +

    Importing data directly from Github into R can be very easy or can require a few steps - depending on the file type. Below are some approaches:

    +
    +

    CSV files

    +

    It can be easy to import a .csv file directly from Github into R with an R command.

    +
      +
    1. Go to the Github repo, locate the file of interest, and click on it.
      +
    2. +
    3. Click on the “Raw” button (you will then see the “raw” csv data, as shown below).
      +
    4. +
    5. Copy the URL (web address).
      +
    6. +
    7. Place the URL in quotes within the import() R command.
    8. +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    XLSX files

    +

    You may not be able to view the “Raw” data for some files (e.g. .xlsx, .rds, .nwk, .shp)

    +
      +
    1. Go to the Github repo, locate the file of interest, and click on it
      +
    2. +
    3. Click the “Download” button, as shown below
      +
    4. +
    5. Save the file on your computer, and import it into R
    6. +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    +
    +
    +

    Shapefiles

    +

    Shapefiles have many sub-component files, each with a different file extention. One file will have the “.shp” extension, but others may have “.dbf”, “.prj”, etc. To download a shapefile from Github, you will need to download each of the sub-component files individually, and save them in the same folder on your computer. In Github, click on each file individually and download them by clicking on the “Download” button.

    +

    Once saved to your computer you can import the shapefile as shown in the GIS basics page using st_read() from the sf package. You only need to provide the filepath and name of the “.shp” file - as long as the other related files are within the same folder on your computer.

    +

    Below, you can see how the shapefile “sle_adm3” consists of many files - each of which must be downloaded from Github.

    +
    +
    +
    +
    +

    +
    +
    +
    +
    + +
    +
    +
    +

    7.8 Manual data entry

    +
    +

    Entry by rows

    +

    Use the tribble function from the tibble package from the tidyverse (online tibble reference).

    +

    Note how column headers start with a tilde (~). Also note that each column must contain only one class of data (character, numeric, etc.). You can use tabs, spacing, and new rows to make the data entry more intuitive and readable. Spaces do not matter between values, but each row is represented by a new line of code. For example:

    +
    +
    # create the dataset manually by row
    +manual_entry_rows <- tibble::tribble(
    +  ~colA, ~colB,
    +  "a",   1,
    +  "b",   2,
    +  "c",   3
    +  )
    +
    +

    And now we display the new dataset:

    +
    +
    +
    + +
    +
    +
    +
    +

    Entry by columns

    +

    Since a data frame consists of vectors (vertical columns), the base approach to manual dataframe creation in R expects you to define each column and then bind them together. This can be counter-intuitive in epidemiology, as we usually think about our data in rows (as above).

    +
    +
    # define each vector (vertical column) separately, each with its own name
    +PatientID <- c(235, 452, 778, 111)
    +Treatment <- c("Yes", "No", "Yes", "Yes")
    +Death     <- c(1, 0, 1, 0)
    +
    +

    CAUTION: All vectors must be the same length (same number of values).

    +

    The vectors can then be bound together using the function data.frame():

    +
    +
    # combine the columns into a data frame, by referencing the vector names
    +manual_entry_cols <- data.frame(PatientID, Treatment, Death)
    +
    +

    And now we display the new dataset:

    +
    +
    +
    + +
    +
    +
    +
    +

    Pasting from clipboard

    +

    If you copy data from elsewhere and have it on your clipboard, you can try one of the two ways below:

    +

    From the clipr package, you can use read_clip_tbl() to import as a data frame, or just just read_clip() to import as a character vector. In both cases, leave the parentheses empty.

    +
    +
    linelist <- clipr::read_clip_tbl()  # imports current clipboard as data frame
    +linelist <- clipr::read_clip()      # imports as character vector
    +
    +

    You can also easily export to your system’s clipboard with clipr. See the section below on Export.

    +

    Alternatively, you can use the the read.table() function from base R with file = "clipboard") to import as a data frame:

    +
    +
    df_from_clipboard <- read.table(
    +  file = "clipboard",  # specify this as "clipboard"
    +  sep = "t",           # separator could be tab, or commas, etc.
    +  header=TRUE)         # if there is a header row
    +
    +
    +
    +
    +

    7.9 Import most recent file

    +

    Often you may receive daily updates to your datasets. In this case you will want to write code that imports the most recent file. Below we present two ways to approach this:

    +
      +
    • Selecting the file based on the date in the file name
      +
    • +
    • Selecting the file based on file metadata (last modification)
    • +
    +
    +

    Dates in file name

    +

    This approach depends on three premises:

    +
      +
    1. You trust the dates in the file names.
      +
    2. +
    3. The dates are numeric and appear in generally the same format (e.g. year then month then day).
      +
    4. +
    5. There are no other numbers in the file name.
    6. +
    +

    We will explain each step, and then show you them combined at the end.

    +

    First, use dir() from base R to extract just the file names for each file in the folder of interest. See the page on Directory interactions for more details about dir(). In this example, the folder of interest is the folder “linelists” within the folder “example” within “data” within the R project.

    +
    +
    linelist_filenames <- dir(here("data", "example", "linelists")) # get file names from folder
    +linelist_filenames                                              # print
    +
    +
    [1] "20201007linelist.csv"          "case_linelist_2020-10-02.csv" 
    +[3] "case_linelist_2020-10-03.csv"  "case_linelist_2020-10-04.csv" 
    +[5] "case_linelist_2020-10-05.csv"  "case_linelist_2020-10-08.xlsx"
    +[7] "case_linelist20201006.csv"    
    +
    +
    +

    Once you have this vector of names, you can extract the dates from them by applying str_extract() from stringr using this regular expression. It extracts any numbers in the file name (including any other characters in the middle such as dashes or slashes). You can read more about stringr in the Strings and characters page.

    +
    +
    linelist_dates_raw <- stringr::str_extract(linelist_filenames, "[0-9].*[0-9]") # extract numbers and any characters in between
    +linelist_dates_raw  # print
    +
    +
    [1] "20201007"   "2020-10-02" "2020-10-03" "2020-10-04" "2020-10-05"
    +[6] "2020-10-08" "20201006"  
    +
    +
    +

    Assuming the dates are written in generally the same date format (e.g. Year then Month then Day) and the years are 4-digits, you can use lubridate’s flexible conversion functions (ymd(), dmy(), or mdy()) to convert them to dates. For these functions, the dashes, spaces, or slashes do not matter, only the order of the numbers. Read more in the Working with dates page.

    +
    +
    linelist_dates_clean <- lubridate::ymd(linelist_dates_raw)
    +linelist_dates_clean
    +
    +
    [1] "2020-10-07" "2020-10-02" "2020-10-03" "2020-10-04" "2020-10-05"
    +[6] "2020-10-08" "2020-10-06"
    +
    +
    +

    The base R function which.max() can then be used to return the index position (e.g. 1st, 2nd, 3rd, …) of the maximum date value. The latest file is correctly identified as the 6th file - “case_linelist_2020-10-08.xlsx”.

    +
    +
    index_latest_file <- which.max(linelist_dates_clean)
    +index_latest_file
    +
    +
    [1] 6
    +
    +
    +

    If we condense all these commands, the complete code could look like below. Note that the . in the last line is a placeholder for the piped object at that point in the pipe sequence. At that point the value is simply the number 6. This is placed in double brackets to extract the 6th element of the vector of file names produced by dir().

    +
    +
    # load packages
    +pacman::p_load(
    +  tidyverse,         # data management
    +  stringr,           # work with strings/characters
    +  lubridate,         # work with dates
    +  rio,               # import / export
    +  here,              # relative file paths
    +  fs)                # directory interactions
    +
    +# extract the file name of latest file
    +latest_file <- dir(here("data", "example", "linelists")) %>%  # file names from "linelists" sub-folder          
    +  str_extract("[0-9].*[0-9]") %>%                  # pull out dates (numbers)
    +  ymd() %>%                                        # convert numbers to dates (assuming year-month-day format)
    +  which.max() %>%                                  # get index of max date (latest file)
    +  dir(here("data", "example", "linelists"))[[.]]              # return the filename of latest linelist
    +
    +latest_file  # print name of latest file
    +
    +
    [1] "case_linelist_2020-10-08.xlsx"
    +
    +
    +

    You can now use this name to finish the relative file path, with here():

    +
    +
    here("data", "example", "linelists", latest_file) 
    +
    +

    And you can now import the latest file:

    +
    +
    # import
    +import(here("data", "example", "linelists", latest_file)) # import 
    +
    +
    +
    +

    Use the file info

    +

    If your files do not have dates in their names (or you do not trust those dates), you can try to extract the last modification date from the file metadata. Use functions from the package fs to examine the metadata information for each file, which includes the last modification time and the file path.

    +

    Below, we provide the folder of interest to fs’s dir_info(). In this case, the folder of interest is in the R project in the folder “data”, the sub-folder “example”, and its sub-folder “linelists”. The result is a data frame with one line per file and columns for modification_time, path, etc. You can see a visual example of this in the page on Directory interactions.

    +

    We can sort this data frame of files by the column modification_time, and then keep only the top/latest row (file) with base R’s head(). Then we can extract the file path of this latest file only with the dplyr function pull() on the column path. Finally we can pass this file path to import(). The imported file is saved as latest_file.

    +
    +
    latest_file <- dir_info(here("data", "example", "linelists")) %>%  # collect file info on all files in directory
    +  arrange(desc(modification_time)) %>%      # sort by modification time
    +  head(1) %>%                               # keep only the top (latest) file
    +  pull(path) %>%                            # extract only the file path
    +  import()                                  # import the file
    +
    + +
    +
    +
    +

    7.10 APIs

    +

    An “Automated Programming Interface” (API) can be used to directly request data from a website. APIs are a set of rules that allow one software application to interact with another. The client (you) sends a “request” and receives a “response” containing content. The R packages httr and jsonlite can facilitate this process.

    +

    Each API-enabled website will have its own documentation and specifics to become familiar with. Some sites are publicly available and can be accessed by anyone. Others, such as platforms with user IDs and credentials, require authentication to access their data.

    +

    Needless to say, it is necessary to have an internet connection to import data via API. We will briefly give examples of use of APIs to import data, and link you to further resources.

    +

    Note: recall that data may be posted* on a website without an API, which may be easier to retrieve. For example a posted CSV file may be accessible simply by providing the site URL to import() as described in the section on importing from Github.*

    +
    +

    HTTP request

    +

    The API exchange is most commonly done through an HTTP request. HTTP is Hypertext Transfer Protocol, and is the underlying format of a request/response between a client and a server. The exact input and output may vary depending on the type of API but the process is the same - a “Request” (often HTTP Request) from the user, often containing a query, followed by a “Response”, containing status information about the request and possibly the requested content.

    +

    Here are a few components of an HTTP request:

    +
      +
    • The URL of the API endpoint.
      +
    • +
    • The “Method” (or “Verb”).
      +
    • +
    • Headers.
      +
    • +
    • Body.
    • +
    +

    The HTTP request “method” is the action your want to perform. The two most common HTTP methods are GET and POST but others could include PUT, DELETE, PATCH, etc. When importing data into R it is most likely that you will use GET.

    +

    After your request, your computer will receive a “response” in a format similar to what you sent, including URL, HTTP status (Status 200 is what you want!), file type, size, and the desired content. You will then need to parse this response and turn it into a workable data frame within your R environment.

    +
    +
    +

    Packages

    +

    The httr package works well for handling HTTP requests in R. It requires little prior knowledge of Web APIs and can be used by people less familiar with software development terminology. In addition, if the HTTP response is .json, you can use jsonlite to parse the response.

    +
    +
    # load packages
    +pacman::p_load(httr, jsonlite, tidyverse)
    +
    +
    +
    +

    Publicly-available data

    +

    Below is an example of an HTTP request, borrowed from a tutorial from the Trafford Data Lab. This site has several other resources to learn and API exercises.

    +

    Scenario: We want to import a list of fast food outlets in the city of Trafford, UK. The data can be accessed from the API of the Food Standards Agency, which provides food hygiene rating data for the United Kingdom.

    +

    Here are the parameters for our request:

    +
      +
    • HTTP verb: GET
      +
    • +
    • API endpoint URL: http://api.ratings.food.gov.uk/Establishments
      +
    • +
    • Selected parameters: name, address, longitude, latitude, businessTypeId, ratingKey, localAuthorityId
      +
    • +
    • Headers: “x-api-version”, 2
      +
    • +
    • Data format(s): JSON, XML
      +
    • +
    • Documentation: http://api.ratings.food.gov.uk/help
    • +
    +

    The R code would be as follows:

    +
    +
    # prepare the request
    +path <- "http://api.ratings.food.gov.uk/Establishments"
    +request <- GET(url = path,
    +             query = list(
    +               localAuthorityId = 188,
    +               BusinessTypeId = 7844,
    +               pageNumber = 1,
    +               pageSize = 5000),
    +             add_headers("x-api-version" = "2"))
    +
    +# check for any server error ("200" is good!)
    +request$status_code
    +
    +# submit the request, parse the response, and convert to a data frame
    +response <- content(request, as = "text", encoding = "UTF-8") %>%
    +  fromJSON(flatten = TRUE) %>%
    +  pluck("establishments") %>%
    +  as_tibble()
    +
    +

    You can now clean and use the response data frame, which contains one row per fast food facility.

    +
    +
    +

    Authentication required

    +

    Some APIs require authentication - for you to prove who you are, so you can access restricted data. To import these data, you may need to first use a POST method to provide a username, password, or code. This will return an access token, that can be used for subsequent GET method requests to retrieve the desired data.

    +

    Below is an example of querying data from Go.Data, which is an outbreak investigation tool. Go.Data uses an API for all interactions between the web front-end and smartphone applications used for data collection. Go.Data is used throughout the world. Because outbreak data are sensitive and you should only be able to access data for your outbreak, authentication is required.

    +

    Below is some sample R code using httr and jsonlite for connecting to the Go.Data API to import data on contact follow-up from your outbreak.

    +
    +
    # set credentials for authorization
    +url <- "https://godatasampleURL.int/"           # valid Go.Data instance url
    +username <- "username"                          # valid Go.Data username 
    +password <- "password"                          # valid Go,Data password 
    +outbreak_id <- "xxxxxx-xxxx-xxxx-xxxx-xxxxxxx"  # valid Go.Data outbreak ID
    +
    +# get access token
    +url_request <- paste0(url,"api/oauth/token?access_token=123") # define base URL request
    +
    +# prepare request
    +response <- POST(
    +  url = url_request,  
    +  body = list(
    +    username = username,    # use saved username/password from above to authorize                               
    +    password = password),                                       
    +    encode = "json")
    +
    +# execute request and parse response
    +content <-
    +  content(response, as = "text") %>%
    +  fromJSON(flatten = TRUE) %>%          # flatten nested JSON
    +  glimpse()
    +
    +# Save access token from response
    +access_token <- content$access_token    # save access token to allow subsequent API calls below
    +
    +# import outbreak contacts
    +# Use the access token 
    +response_contacts <- GET(
    +  paste0(url,"api/outbreaks/",outbreak_id,"/contacts"),          # GET request
    +  add_headers(
    +    Authorization = paste("Bearer", access_token, sep = " ")))
    +
    +json_contacts <- content(response_contacts, as = "text")         # convert to text JSON
    +
    +contacts <- as_tibble(fromJSON(json_contacts, flatten = TRUE))   # flatten JSON to tibble
    +
    +

    CAUTION: If you are importing large amounts of data from an API requiring authentication, it may time-out. To avoid this, retrieve access_token again before each API GET request and try using filters or limits in the query.

    +

    TIP: The fromJSON() function in the jsonlite package does not fully un-nest the first time it’s executed, so you will likely still have list items in your resulting tibble. You will need to further un-nest for certain variables; depending on how nested your .json is. To view more info on this, view the documentation for the jsonlite package, such as the flatten() function.

    +

    For more details, View documentation on LoopBack Explorer, the Contact Tracing page or API tips on Go.Data Github repository

    +

    You can read more about the httr package here

    +

    This section was also informed by this tutorial and this tutorial.

    + +
    +
    +
    +

    7.11 Export

    +
    +

    With rio package

    +

    With rio, you can use the export() function in a very similar way to import(). First give the name of the R object you want to save (e.g. linelist) and then in quotes put the file path where you want to save the file, including the desired file name and file extension. For example:

    +

    This saves the data frame linelist as an Excel workbook to the working directory/R project root folder:

    +
    +
    export(linelist, "my_linelist.xlsx") # will save to working directory
    +
    +

    You could save the same data frame as a csv file by changing the extension. For example, we also save it to a file path constructed with here():

    +
    +
    export(linelist, here("data", "clean", "my_linelist.csv"))
    +
    +
    +
    +

    To clipboard

    +

    To export a data frame to your computer’s “clipboard” (to then paste into another software like Excel, Google Spreadsheets, etc.) you can use write_clip() from the clipr package.

    +
    +
    # export the linelist data frame to your system's clipboard
    +clipr::write_clip(linelist)
    +
    +
    +
    +
    +

    7.12 RDS files

    +

    Along with .csv, .xlsx, etc, you can also export (save) R data frames as .rds files. This is a file format specific to R, and is very useful if you know you will work with the exported data again in R.

    +

    The classes of columns are stored, so you don’t have do to cleaning again when it is imported (with an Excel or even a CSV file this can be a headache!). It is also a smaller file, which is useful for export and import if your dataset is large.

    +

    For example, if you work in an Epidemiology team and need to send files to a GIS team for mapping, and they use R as well, just send them the .rds file! Then all the column classes are retained and they have less work to do.

    +
    +
    export(linelist, here("data", "clean", "my_linelist.rds"))
    +
    + +
    +
    +

    7.13 Rdata files and lists

    +

    .Rdata files can store multiple R objects - for example multiple data frames, model results, lists, etc. This can be very useful to consolidate or share a lot of your data for a given project.

    +

    In the below example, multiple R objects are stored within the exported file “my_objects.Rdata”:

    +
    +
    rio::export(my_list, my_dataframe, my_vector, "my_objects.Rdata")
    +
    +

    Note: if you are trying to import a list, use import_list() from rio to import it with the complete original structure and contents.

    +
    +
    rio::import_list("my_list.Rdata")
    +
    + +
    +
    +

    7.14 Saving plots

    +

    Instructions on how to save plots, such as those created by ggplot(), are discussed in depth in the ggplot basics page.

    +

    In brief, run ggsave("my_plot_filepath_and_name.png") after printing your plot. You can either provide a saved plot object to the plot = argument, or only specify the destination file path (with file extension) to save the most recently-displayed plot. You can also control the width =, height =, units =, and dpi =.

    +

    How to save a network graph, such as a transmission tree, is addressed in the page on Transmission chains.

    + +
    +
    +

    7.15 Resources

    +

    R Data Import/Export Manual
    +R 4 Data Science chapter on data import
    +ggsave() documentation

    +

    Below is a table, taken from the rio online vignette. For each type of data it shows: the expected file extension, the package rio uses to import or export the data, and whether this functionality is included in the default installed version of rio.

    + +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FormatTypical ExtensionImport PackageExport PackageInstalled by Default
    Comma-separated data.csvdata.table fread()data.tableYes
    Pipe-separated data.psvdata.table fread()data.tableYes
    Tab-separated data.tsvdata.table fread()data.tableYes
    SAS.sas7bdathavenhavenYes
    SPSS.savhavenhavenYes
    Stata.dtahavenhavenYes
    SASXPORT.xpthavenhaven
    SPSS Portable.porhavenYes
    Excel.xlsreadxlYes
    Excel.xlsxreadxlopenxlsxYes
    R syntax.RbasebaseYes
    Saved R objects.RData, .rdabasebaseYes
    Serialized R objects.rdsbasebaseYes
    Epiinfo.recforeignYes
    Minitab.mtpforeignYes
    Systat.sydforeignYes
    “XBASE”database files.dbfforeignforeign
    Weka Attribute-Relation File Format.arffforeignforeignYes
    Data Interchange Format.difutilsYes
    Fortran datano recognized extensionutilsYes
    Fixed-width format data.fwfutilsutilsYes
    gzip comma-separated data.csv.gzutilsutilsYes
    CSVY (CSV + YAML metadata header).csvycsvycsvyNo
    EViews.wf1hexViewNo
    Feather R/Python interchange format.featherfeatherfeatherNo
    Fast Storage.fstfstfstNo
    JSON.jsonjsonlitejsonliteNo
    Matlab.matrmatiormatioNo
    OpenDocument Spreadsheet.odsreadODSreadODSNo
    HTML Tables.htmlxml2xml2No
    Shallow XML documents.xmlxml2xml2No
    YAML.ymlyamlyamlNo
    Clipboard default is tsvcliprcliprNo
    + + +
    + +
    + + +
    + + + + + + + \ No newline at end of file diff --git a/new_pages/importing.qmd b/new_pages/importing.qmd index 1429fd59..4b9a75ff 100644 --- a/new_pages/importing.qmd +++ b/new_pages/importing.qmd @@ -5,12 +5,11 @@ knitr::include_graphics(here::here("images", "Import_Export_1500x500.png")) ``` - - In this page we describe ways to locate, import, and export files: -* Use of the **rio** package to flexibly `import()` and `export()` many types of files -* Use of the **here** package to locate files relative to an R project root - to prevent complications from file paths that are specific to one computer +* Use of the **rio** package to flexibly `import()` and `export()` many types of files +* Use of R projects to store your data files, and locate them easily from any computer by using *relative file paths* +* Use of the **here** package to create the file paths * Specific import scenarios, such as: * Specific Excel sheets * Messy headers and skipping rows @@ -30,17 +29,21 @@ pacman::p_load( tidyverse) # data management, summary, and visualization ``` + + ## Overview -When you import a "dataset" into R, you are generally creating a new *data frame* object in your R environment and defining it as an imported file (e.g. Excel, CSV, TSV, RDS) that is located in your folder directories at a certain file path/address. +When you import a "dataset" into R, you are generally creating a new *data frame* object in your R environment and defining it as an imported file (e.g. Excel, CSV, TSV, RDS) which is located in your folder directories at a certain file path/address. You can import/export many types of files, including those created by other statistical programs (SAS, STATA, SPSS). You can also connect to relational databases. R even has its own data formats: -* An RDS file (.rds) stores a single R object such as a data frame. These are useful to store cleaned data, as they maintain R column classes. Read more in [this section](#import_rds). -* An RData file (.Rdata) can be used to store multiple objects, or even a complete R workspace. Read more in [this section](#import_rdata). +* An RDS file (.rds) stores a single R object such as a data frame. These are useful to store cleaned data, as they maintain R column classes. Read more in [this section](#import_rds) +* An RData file (.Rdata) can be used to store multiple objects, or even a complete R workspace. Read more in [this section](#import_rdata) + + @@ -50,6 +53,13 @@ The R package we recommend is: **rio**. The name "rio" is an abbreviation of "R Its functions `import()` and `export()` can handle many different file types (e.g. .xlsx, .csv, .rds, .tsv). When you provide a file path to either of these functions (including the file extension like ".csv"), **rio** will read the extension and use the correct tool to import or export the file. +If used within an R project, an importing command can be as simple as: + +```{r, eval=FALSE, warning=F, message=F} +linelist <- import("linelist_raw.xlsx") +``` + + The alternative to using **rio** is to use functions from many other packages, each of which is specific to a type of file. For example, `read.csv()` (**base** R), `read.xlsx()` (**openxlsx** package), and `write_csv()` (**readr** pacakge), etc. These alternatives can be difficult to remember, whereas using `import()` and `export()` from **rio** is easy. **rio**'s functions `import()` and `export()` use the appropriate package and function for a given file, based on its file extension. See the end of this page for a complete table of which packages/functions **rio** uses in the background. It can also be used to import STATA, SAS, and SPSS files, among dozens of other file types. @@ -60,82 +70,101 @@ Import/export of shapefiles requires other packages, as detailed in the page on -## The **here** package {#here} + +## File paths + +When importing or exporting data, you must provide a file path. You can do this one of three ways: +1) Provide the "full" / "absolute" file path (*not recommended*) +2) Provide a "relative" file path (*recommended*) +3) Manual file selection -The package **here** and its function `here()` make it easy to tell R where to find and to save your files - in essence, it builds file paths. -Used in conjunction with an R project, **here** allows you to describe the location of files in your R project in relation to the R project's *root directory* (the top-level folder). This is useful when the R project may be shared or accessed by multiple people/computers. It prevents complications due to the unique file paths on different computers (e.g. `"C:/Users/Laura/Documents..."` by "starting" the file path in a place common to all users (the R project root). -This is how `here()` works within an R project: +### "Absolute" file paths {.unnumbered} -* When the **here** package is first loaded within the R project, it places a small file called ".here" in the root folder of your R project as a "benchmark" or "anchor" -* In your scripts, to reference a file in the R project's sub-folders, you use the function `here()` to build the file path *in relation to that anchor* -* To build the file path, write the names of folders beyond the root, within quotes, separated by commas, finally ending with the file name and file extension as shown below -* `here()` file paths can be used for both importing and exporting +Absolute or "full" file paths can be provided to functions like `import()` but they are "fragile" as they are unique to the user's specific computer and therefore *not recommended*. -For example, below, the function `import()` is being provided a file path constructed with `here()`. +Below is an example of a command using an absolute file path. In Laura's computer there is a folder called "analysis", a sub-folder "data", and within that a sub-folder "linelists", in which there is the .xlsx file of interest. ```{r, eval=F} -linelist <- import(here("data", "linelists", "ebola_linelist.xlsx")) +linelist <- import("C:/Users/Laura/Documents/analysis/data/linelists/linelist_raw.xlsx") ``` -The command `here("data", "linelists", "ebola_linelist.xlsx")` is actually providing the full file path that is *unique to the user's computer*: +A few things to note about absolute file paths: -``` -"C:/Users/Laura/Documents/my_R_project/data/linelists/ebola_linelist.xlsx" -``` +* **Avoid using absolute file paths** as they will not work if the script is run on a different computer +* Provide the file path within quotation marks. It is a "string" (character) value +* Use *forward* slashes (`/`), as in the example above (note: this is *NOT* the default for Windows file paths) +* File paths that begin with double slashes (e.g. "//...") will likely **not be recognized by R** and will produce an error. Consider moving your work to a "named" or "lettered" drive that begins with a letter (e.g. "J:" or "C:"). See the page on [Directory interactions](directories.qmd) for more details on this issue -The beauty is that the R command using `here()` can be successfully run on any computer accessing the R project. +One scenario where absolute file paths may be appropriate is when you want to import a file from a shared drive that has the same full file path for all users. +**_TIP:_** To quickly convert all `\` to `/`, highlight the code of interest, use Ctrl+f (in Windows), check the option box for "In selection", and then use the replace functionality to convert them. -**_TIP:_** If you are unsure where the “.here” root is set to, run the function `here()` with empty parentheses. -Read more about the **here** package [at this link](https://here.r-lib.org/). +### R Projects and "relative" file paths {.unnumbered} +In R, "relative" file paths consist of the file path *relative to* the root of an **R project**. This allows for more simple commands which can be run from different computers (e.g. if the R project is on a shared drive or is sent by email). - -## File paths +Let us assume that our work is in an R project that contains a sub-folder "data" and within that a subfolder "linelists", in which there is the .xlsx file of interest. -When importing or exporting data, you must provide a file path. You can do this one of three ways: +The "absolute" file path could be: -1) *Recommended:* provide a "relative" file path with the **here** package -2) Provide the "full" / "absolute" file path -3) Manual file selection +``` +"C:/Users/Laura/Documents/my_R_project/data/linelists/linelist_raw.xlsx" +``` +By working within an R project, we can import the data by simply writing this command: +```{r, eval=FALSE, warning=F, message=F} +linelist <- import("data/linelists/linelist_raw.xlsx") +``` -### "Relative" file paths {.unnumbered} +Because we are using an R project, R knows to begin its search for the file in the project's folder. Then the command tells it to look in the "data" folder, and then the "linelists" folder, and to find the dataset. -In R, "relative" file paths consist of the file path *relative to* the root of an R project. They allow for more simple file paths that can work on different computers (e.g. if the R project is on a shared drive or is sent by email). As described [above](#here), relative file paths are facilitated by use of the **here** package. -An example of a relative file path constructed with `here()` is below. We assume the work is in an R project that contains a sub-folder "data" and within that a subfolder "linelists", in which there is the .xlsx file of interest. -```{r, eval=F} -linelist <- import(here("data", "linelists", "ebola_linelist.xlsx")) -``` +### The **here** package {#here} +The importing command can be improved by building the relative file path via the package **here** and its function `here()`. +The file path to the .xlsx file can be created with the below command. Note how each folder *after the R project*, and the file name itself, is listed within quotation marks and separated by commas. -### "Absolute" file paths {.unnumbered} +```{r, eval=F, message = F, warning = F} +here("data", "linelists", "linelist_raw.xlsx") +``` -Absolute or "full" file paths can be provided to functions like `import()` but they are "fragile" as they are unique to the user's specific computer and therefore *not recommended*. +Because this command is run within an R Project, it will **return** a full, absolute file path that is *adapted to the user's computer*, such as below. + +``` +"C:/Users/Laura/Documents/my_R_project/data/linelists/linelist_raw.xlsx" +``` -Below is an example of an absolute file path, where in Laura's computer there is a folder "analysis", a sub-folder "data" and within that a sub-folder "linelists", in which there is the .xlsx file of interest. +The final step is to *nest the `here()` command within the `import()` function*, like this: ```{r, eval=F} -linelist <- import("C:/Users/Laura/Documents/analysis/data/linelists/ebola_linelist.xlsx") +linelist <- import(here("data", "linelists", "linelist_raw.xlsx")) ``` -A few things to note about absolute file paths: +There are several benefits to using `here()` within `import()`: + +1) `here()` becomes very important when creating *automated reports* with R Markdown or Quarto +2) `here()` free you from worrying about the slash direction (see example above) + + + +**_TIP:_** If you are unsure where the "here" root is set to, run the function `here()` with empty parentheses and then begin building the command. + +Read more about the **here** package [at this link](https://here.r-lib.org/). + + + + + -* **Avoid using absolute file paths** as they will break if the script is run on a different computer -* Use *forward* slashes (`/`), as in the example above (note: this is *NOT* the default for Windows file paths) -* File paths that begin with double slashes (e.g. "//...") will likely **not be recognized by R** and will produce an error. Consider moving your work to a "named" or "lettered" drive that begins with a letter (e.g. "J:" or "C:"). See the page on [Directory interactions](directories.qmd) for more details on this issue. -One scenario where absolute file paths may be appropriate is when you want to import a file from a shared drive that has the same full file path for all users. -**_TIP:_** To quickly convert all `\` to `/`, highlight the code of interest, use Ctrl+f (in Windows), check the option box for "In selection", and then use the replace functionality to convert them. @@ -144,8 +173,8 @@ One scenario where absolute file paths may be appropriate is when you want to im You can import data manually via one of these methods: -1) Environment RStudio Pane, click "Import Dataset", and select the type of data -2) Click File / Import Dataset / (select the type of data) +1) Environment RStudio Pane, click "Import Dataset", and select the type of data +2) Click File / Import Dataset / (select the type of data) 3) To hard-code manual selection, use the *base R* command `file.choose()` (leaving the parentheses empty) to trigger appearance of a **pop-up window** that allows the user to manually select the file from their computer. For example: ```{r import_choose, eval=F} @@ -159,6 +188,11 @@ my_data <- import(file.choose()) + + + + + ## Import data To use `import()` to import a dataset is quite simple. Simply provide the path to the file (including the file name and file extension) in quotes. If using `here()` to build the file path, follow the instructions above. Below are a few examples: @@ -195,7 +229,7 @@ By default, if you provide an Excel workbook (.xlsx) to `import()`, the workbook my_data <- import("my_excel_file.xlsx", which = "Sheetname") ``` -If using the `here()` method to provide a relative pathway to `import()`, you can still indicate a specific sheet by adding the `which = ` argument after the closing parentheses of the `here()` function. +If using the `here()` method to create the file path, you can still indicate a specific sheet by adding the `which = ` argument after the closing parentheses of the `here()` function. ```{r import_sheet_here, eval=F} # Demonstration: importing a specific Excel sheet when using relative pathways with the 'here' package @@ -249,7 +283,7 @@ Unfortunately `skip = ` only accepts one integer value, *not* a range (e.g. "2:1 Sometimes, your data may have a *second* row, for example if it is a "data dictionary" row as shown below. This situation can be problematic because it can result in all columns being imported as class "character". -```{r, echo=F} +```{r, echo=F, warning = F, message = F} # HIDDEN FROM READER #################### # Create second header row of "data dictionary" and insert into row 2. Save as new dataframe. @@ -301,7 +335,8 @@ The exact argument used to bind the correct column names depends on the type of ```{r, eval=F} # import first time; store the column names -linelist_raw_names <- import("linelist_raw.xlsx") %>% names() # save true column names +linelist_raw_names <- import("linelist_raw.xlsx") %>% + names() # save true column names # import second time; skip row 2, and assign column names to argument col_names = linelist_raw <- import("linelist_raw.xlsx", @@ -313,8 +348,9 @@ linelist_raw <- import("linelist_raw.xlsx", **For CSV files:** (`col.names = `) ```{r, eval=F} -# import first time; sotre column names -linelist_raw_names <- import("linelist_raw.csv") %>% names() # save true column names +# import first time; save column names +linelist_raw_names <- import("linelist_raw.csv") %>% + names() # save true column names # note argument for csv files is 'col.names = ' linelist_raw <- import("linelist_raw.csv", @@ -333,7 +369,7 @@ colnames(linelist_raw) <- linelist_raw_names #### Make a data dictionary {.unnumbered} -Bonus! If you do have a second row that is a data dictionary, you can easily create a proper data dictionary from it. This tip is adapted from this [post](https://alison.rbind.io/post/2018-02-23-read-multiple-header-rows/). +Bonus! If you do have a second row that is a data dictionary, you can easily create a proper data dictionary from it. This tip is adapted from this [post](https://www.apreshill.com/blog/2018-07-multiple-headers/). ```{r} @@ -392,16 +428,24 @@ Gsheets_demo <- read_sheet("1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY") Another package, **googledrive** offers useful functions for writing, editing, and deleting Google sheets. For example, using the `gs4_create()` and `sheet_write()` functions found in this package. Here are some other helpful online tutorials: -[basic Google sheets importing tutorial](https://arbor-analytics.com/post/getting-your-data-into-r-from-google-sheets/) -[more detailed tutorial](https://googlesheets4.tidyverse.org/articles/googlesheets4.html) -[interaction between the googlesheets4 and tidyverse](https://googlesheets4.tidyverse.org/articles/articles/drive-and-sheets.html) +[Google sheets importing tutorial](https://felixanalytix.medium.com/how-to-read-write-append-google-sheet-data-using-r-programming-ecf278108691). +[More detailed tutorial](https://googlesheets4.tidyverse.org/articles/googlesheets4.html). +[Interaction between the googlesheets4 and tidyverse](https://googlesheets4.tidyverse.org/articles/articles/drive-and-sheets.html). + +Additionally, you can also use `import` from the **rio** package. + +```{r, eval=F} +Gsheets_demo <- rio("https://docs.google.com/spreadsheets/d/1scgtzkVLLHAe5a6_eFQEwkZcc14yFUx1KgOMZ4AKUfY/edit#gid=0") +``` ## Multiple files - import, export, split, combine -See the page on [Iteration, loops, and lists](iteration.qmd) for examples of how to import and combine multiple files, or multiple Excel workbook files. That page also has examples on how to split a data frame into parts and export each one separately, or as named sheets in an Excel workbook. +See the page on [Iteration, loops, and lists](iteration.qmd) for examples of how to import and combine multiple files, or multiple Excel workbook files. + +That page also has examples on how to split a data frame into parts and export each one separately, or as named sheets in an Excel workbook. @@ -415,10 +459,10 @@ Importing data directly from Github into R can be very easy or can require a few It can be easy to import a .csv file directly from Github into R with an R command. -1) Go to the Github repo, locate the file of interest, and click on it -3) Click on the "Raw" button (you will then see the "raw" csv data, as shown below) -4) Copy the URL (web address) -5) Place the URL in quotes within the `import()` R command +1) Go to the Github repo, locate the file of interest, and click on it +3) Click on the "Raw" button (you will then see the "raw" csv data, as shown below) +4) Copy the URL (web address) +5) Place the URL in quotes within the `import()` R command ```{r, out.width=c('100%', '100%'), fig.align = "left", echo=F} knitr::include_graphics(here::here("images", "download_csv_raw.png")) @@ -542,16 +586,16 @@ df_from_clipboard <- read.table( Often you may receive daily updates to your datasets. In this case you will want to write code that imports the most recent file. Below we present two ways to approach this: * Selecting the file based on the date in the file name -* Selecting the file based on file metadata (last modification) +* Selecting the file based on file metadata (last modification) ### Dates in file name {.unnumbered} This approach depends on three premises: -1) You trust the dates in the file names -2) The dates are numeric and appear in *generally* the same format (e.g. year then month then day) -3) There are no other numbers in the file name +1) You trust the dates in the file names +2) The dates are numeric and appear in *generally* the same format (e.g. year then month then day) +3) There are no other numbers in the file name We will explain each step, and then show you them combined at the end. @@ -651,7 +695,7 @@ Each API-enabled website will have its own documentation and specifics to become Needless to say, it is necessary to have an internet connection to import data via API. We will briefly give examples of use of APIs to import data, and link you to further resources. -*Note: recall that data may be *posted* on a website without an API, which may be easier to retrieve. For example a posted CSV file may be accessible simply by providing the site URL to `import()` as described in the section on [importing from Github](#import_github).* +Note: recall that data may be *posted* on a website without an API, which may be easier to retrieve. For example a posted CSV file may be accessible simply by providing the site URL to `import()` as described in the section on [importing from Github](#import_github). ### HTTP request {.unnumbered} @@ -660,10 +704,10 @@ The API exchange is most commonly done through an HTTP request. HTTP is Hypertex Here are a few components of an *HTTP request*: -* The URL of the API endpoint -* The "Method" (or "Verb") -* Headers -* Body +* The URL of the API endpoint +* The "Method" (or "Verb") +* Headers +* Body The HTTP request "method" is the action your want to perform. The two most common HTTP methods are `GET` and `POST` but others could include `PUT`, `DELETE`, `PATCH`, etc. When importing data into R it is most likely that you will use `GET`. @@ -688,12 +732,12 @@ Scenario: We want to import a list of fast food outlets in the city of Trafford, Here are the parameters for our request: -* HTTP verb: GET -* API endpoint URL: http://api.ratings.food.gov.uk/Establishments -* Selected parameters: name, address, longitude, latitude, businessTypeId, ratingKey, localAuthorityId -* Headers: “x-api-version”, 2 -* Data format(s): JSON, XML -* Documentation: http://api.ratings.food.gov.uk/help +* HTTP verb: GET +* API endpoint URL: [http://api.ratings.food.gov.uk/Establishments](http://api.ratings.food.gov.uk/Establishments) +* Selected parameters: name, address, longitude, latitude, businessTypeId, ratingKey, localAuthorityId +* Headers: “x-api-version” +* Data format(s): JSON, XML +* Documentation: [http://api.ratings.food.gov.uk/help](http://api.ratings.food.gov.uk/help) The R code would be as follows: @@ -816,7 +860,7 @@ clipr::write_clip(linelist) ## RDS files {#import_rds} -Along with .csv, .xlsx, etc, you can also export/save R data frames as .rds files. This is a file format specific to R, and is very useful if you know you will work with the exported data again in R. +Along with .csv, .xlsx, etc, you can also export (save) R data frames as .rds files. This is a file format specific to R, and is very useful if you know you will work with the exported data again in R. The classes of columns are stored, so you don't have do to cleaning again when it is imported (with an Excel or even a CSV file this can be a headache!). It is also a smaller file, which is useful for export and import if your dataset is large. @@ -864,8 +908,10 @@ How to save a network graph, such as a transmission tree, is addressed in the pa ## Resources {} -The [R Data Import/Export Manual](https://cran.r-project.org/doc/manuals/r-release/R-data.html) +[R Data Import/Export Manual](https://cran.r-project.org/doc/manuals/r-release/R-data.html) + [R 4 Data Science chapter on data import](https://r4ds.had.co.nz/data-import.html#data-import) + [ggsave() documentation](https://ggplot2.tidyverse.org/reference/ggsave.html) diff --git a/new_pages/interactive_plots.qmd b/new_pages/interactive_plots.qmd index bec9c49e..57f472fd 100644 --- a/new_pages/interactive_plots.qmd +++ b/new_pages/interactive_plots.qmd @@ -21,9 +21,9 @@ p <- linelist %>% date_earliest = if_else(is.na(date_infection), date_onset, date_infection), week_earliest = floor_date(date_earliest, unit = "week",week_start = 1))%>% count(week_earliest, outcome) %>% - ggplot()+ - geom_col(aes(week_earliest, n, fill = outcome))+ - xlab("Week of infection/onset") + ylab("Cases per week")+ + ggplot() + + geom_col(aes(week_earliest, n, fill = outcome)) + + xlab("Week of infection/onset") + ylab("Cases per week") + theme_minimal() p %>% @@ -61,7 +61,7 @@ In this page we assume that you are beginning with a `ggplot()` plot that you wa To begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -111,7 +111,7 @@ DT::datatable(head(weekly_deaths, 50), rownames = FALSE, options = list(pageLeng Then we create the plot with **ggplot2**, using `geom_line()`. ```{r, warning=F, message=F} -deaths_plot <- ggplot(data = weekly_deaths)+ # begin with weekly deaths data +deaths_plot <- ggplot(data = weekly_deaths) + # begin with weekly deaths data geom_line(mapping = aes(x = epiweek, y = pct_death)) # make line deaths_plot # print @@ -120,34 +120,37 @@ deaths_plot # print We can make this interactive by simply passing this plot to `ggplotly()`, as below. Hover your mouse over the line to show the x and y values. You can zoom in on the plot, and drag it around. You can also see icons in the upper-right of the plot. In order, they allow you to: -* Download the current view as a PNG image -* Zoom in with a select box -* "Pan", or move across the plot by clicking and dragging the plot -* Zoom in, zoom out, or return to default zoom -* Reset axes to defaults -* Toggle on/off "spike lines" which are dotted lines from the interactive point extending to the x and y axes -* Adjustments to whether data show when you are not hovering on the line +* Download the current view as a PNG image. +* Zoom in with a select box. +* "Pan", or move across the plot by clicking and dragging the plot. +* Zoom in, zoom out, or return to default zoom. +* Reset axes to defaults. +* Toggle on/off "spike lines" which are dotted lines from the interactive point extending to the x and y axes. +* Adjustments to whether data show when you are not hovering on the line. ```{r} -deaths_plot %>% plotly::ggplotly() +deaths_plot %>% + plotly::ggplotly() ``` Grouped data work with `ggplotly()` as well. Below, a weekly epicurve is made, grouped by outcome. The stacked bars are interactive. Try clicking on the different items in the legend (they will appear/disappear). ```{r plot_show, eval=F} -# Make epidemic curve with incidence2 pacakge +# Make epidemic curve with incidence2 package p <- incidence2::incidence( linelist, - date_index = date_onset, + date_index = "date_onset", interval = "weeks", - groups = outcome) %>% plot(fill = outcome) + groups = "outcome") %>% + plot(fill = "outcome") ``` ```{r, echo=T, eval=F} # Plot interactively -p %>% plotly::ggplotly() +p %>% + plotly::ggplotly() ``` ```{r, warning = F, message = F, , out.width=c('95%'), out.height=c('500px'), echo=FALSE} @@ -220,13 +223,13 @@ agg_weeks <- facility_count_data %>% metrics_plot <- ggplot(agg_weeks, aes(x = week, y = location_name, - fill = p_days_reported))+ - geom_tile(colour="white")+ - scale_fill_gradient(low = "orange", high = "darkgreen", na.value = "grey80")+ + fill = p_days_reported)) + + geom_tile(colour="white") + + scale_fill_gradient(low = "orange", high = "darkgreen", na.value = "grey80") + scale_x_date(expand = c(0,0), date_breaks = "2 weeks", - date_labels = "%d\n%b")+ - theme_minimal()+ + date_labels = "%d\n%b") + + theme_minimal() + theme( legend.title = element_text(size=12, face="bold"), legend.text = element_text(size=10, face="bold"), @@ -238,7 +241,7 @@ metrics_plot <- ggplot(agg_weeks, axis.title = element_text(size=12, face="bold"), plot.title = element_text(hjust=0,size=14,face="bold"), plot.caption = element_text(hjust = 0, face = "italic") - )+ + ) + labs(x = "Week", y = "Facility name", fill = "Reporting\nperformance (%)", @@ -330,6 +333,6 @@ metrics_plot %>% ## Resources { } -Plotly is not just for R, but also works well with Python (and really any data science language as it's built in JavaScript). You can read more about it on the [plotly website](https://plotly.com/r/) +Plotly is not just for R, but also works well with Python and Julia (and really any data science language as it's built in JavaScript). You can read more about it on the [plotly website](https://plotly.com/r/). diff --git a/new_pages/iteration.qmd b/new_pages/iteration.qmd index a40deac7..c3e24ae7 100644 --- a/new_pages/iteration.qmd +++ b/new_pages/iteration.qmd @@ -5,14 +5,14 @@ Epidemiologists often are faced with repeating analyses on subgroups such as cou This page will introduce two approaches to iterative operations - using *for loops* and using the package **purrr**. -1) *for loops* iterate code across a series of inputs, but are less common in R than in other programming languages. Nevertheless, we introduce them here as a learning tool and reference -2) The **purrr** package is the **tidyverse** approach to iterative operations - it works by "mapping" a function across many inputs (values, columns, datasets, etc.) +1) *for loops* iterate code across a series of inputs, but are less common in R than in other programming languages. Nevertheless, we introduce them here as a learning tool and reference. +2) The **purrr** package is the **tidyverse** approach to iterative operations - it works by "mapping" a function across many inputs (values, columns, datasets, etc.). Along the way, we'll show examples like: -* Importing and exporting multiple files -* Creating epicurves for multiple jurisdictions -* Running T-tests for several columns in a data frame +* Importing and exporting multiple files. +* Creating epicurves for multiple jurisdictions. +* Running T-tests for several columns in a data frame. In the **purrr** [section](#iter_purrr) we will also provide several examples of creating and handling `lists`. @@ -40,7 +40,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, message=F, warning=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -75,16 +75,16 @@ You may move quickly through *for loops* to iterating with mapped functions with A *for loop* has three core parts: -1) The **sequence** of items to iterate through -2) The **operations** to conduct per item in the sequence -3) The **container** for the results (optional) +1) The **sequence** of items to iterate through. +2) The **operations** to conduct per item in the sequence. +3) The **container** for the results (optional). The basic syntax is: `for (item in sequence) {do operations using item}`. Note the parentheses and the curly brackets. The results could be printed to console, or stored in a container R object. A simple *for loop* example is below. ```{r} -for (num in c(1,2,3,4,5)) { # the SEQUENCE is defined (numbers 1 to 5) and loop is opened with "{" +for (num in c(1, 2, 3, 4, 5)) { # the SEQUENCE is defined (numbers 1 to 5) and loop is opened with "{" print(num + 2) # The OPERATIONS (add two to each sequence number and print) } # The loop is closed with "}" # There is no "container" in this example @@ -94,7 +94,7 @@ for (num in c(1,2,3,4,5)) { # the SEQUENCE is defined (numbers 1 to 5) and loop ### Sequence {.unnumbered} -This is the "for" part of a *for loop* - the operations will run "for" each item in the sequence. The sequence can be a series of values (e.g. names of jurisdictions, diseases, column names, list elements, etc), or it can be a series of consecutive numbers (e.g. 1,2,3,4,5). Each approach has their own utilities, described below. +This is the "for" part of a *for loop* - the operations will run "for" each item in the sequence. The sequence can be a series of values (e.g. names of jurisdictions, diseases, column names, list elements, etc), or it can be a series of consecutive numbers (e.g. 1, 2, 3, 4, 5). Each approach has their own utilities, described below. The basic structure of a sequence statement is `item in vector`. @@ -111,7 +111,7 @@ hospital_names <- unique(linelist$hospital) hospital_names # print ``` -We have chosen the term `hosp` to represent values from the vector `hospital_names`. For the first iteration of the loop, the value of `hosp` will be ` hospital_names[[1]]`. For the second loop it will be ` hospital_names[[2]]`. And so on... +We have chosen the term `hosp` to represent values from the vector `hospital_names`. For the first iteration of the loop, the value of `hosp` will be ` hospital_names[[1]]`. For the second loop it will be ` hospital_names[[2]]`. And so on. ```{r, eval=F} # a 'for loop' with character sequence @@ -130,7 +130,7 @@ Below, the sequence is the `names()` (column names) of the `linelist` data frame For purposes of example, we include operations code inside the *for loop*, which is run for every value in the sequence. In this code, the sequence values (column names) are used to *index* (subset) `linelist`, one-at-a-time. As taught in the [R basics](basics.qmd) page, double branckets `[[ ]]` are used to subset. The resulting column is passed to `is.na()`, then to `sum()` to produce the number of values in the column that are missing. The result is printed to the console - one number for each column. -A note on indexing with column names - whenever referencing the column itself *do not just write "col"!* `col` represents just the character column name! To refer to the entire column you must use the column name as an *index* on `linelist` via `linelist[[col]]`. +A note on indexing with column names - whenever referencing the column itself **do not just write "col"!** `col` represents just the character column name! To refer to the entire column you must use the column name as an *index* on `linelist` via `linelist[[col]]`. ```{r} for (col in names(linelist)){ # loop runs for each column in linelist; column name represented by "col" @@ -213,8 +213,9 @@ Say you want to store the median delay-to-admission for each hospital. You would ```{r} delays <- vector( - mode = "double", # we expect to store numbers - length = length(unique(linelist$hospital))) # the number of unique hospitals in the dataset + mode = "double", # we expect to store numbers + length = length(unique(linelist$hospital)) # the number of unique hospitals in the dataset + ) ``` **Empty data frame** @@ -269,15 +270,16 @@ We can make a nice epicurve of *all* the cases by gender using the **incidence2* ```{r, warning=F, message=F} # create 'incidence' object outbreak <- incidence2::incidence( - x = linelist, # dataframe - complete linelist - date_index = "date_onset", # date column - interval = "week", # aggregate counts weekly - groups = "gender") # group values by gender - #na_as_group = TRUE) # missing gender is own group - -# tracer la courbe d'épidémie -ggplot(outbreak, # nom de l'objet d'incidence - aes(x = date_index, #aesthetiques et axes + x = linelist, # dataframe - complete linelist + date_index = "date_onset", # date column + interval = "week", # aggregate counts weekly + groups = "gender" # group values by gender + ) + #na_as_group = TRUE) # missing gender is own group + +# plot +ggplot(outbreak, + aes(x = date_index, y = count, fill = gender), # Fill colour of bars by gender color = "black" # Contour colour of bars @@ -289,8 +291,11 @@ ggplot(outbreak, # nom de l'objet d'incidence x = "Counts", y = "Date", fill = "Gender", - color = "Gender") - + color = "Gender") + + theme(axis.title.x = element_blank(), + axis.text.x = element_blank(), + axis.ticks.x = element_blank()) + ``` @@ -300,13 +305,13 @@ First, we save a named vector of the unique hospital names, `hospital_names`. Th Within the loop operations, you can write R code as normal, but use the "item" (`hosp` in this case) knowing that its value will be changing. Within this loop: -* A `filter()` is applied to `linelist`, such that column `hospital` must equal the current value of `hosp` -* The incidence object is created on the filtered linelist -* The plot for the current hospital is created, with an auto-adjusting title that uses `hosp` -* The plot for the current hospital is temporarily saved and then printed -* The loop then moves onward to repeat with the next hospital in `hospital_names` +* A `filter()` is applied to `linelist`, such that column `hospital` must equal the current value of `hosp`. +* The incidence object is created on the filtered linelist. +* The plot for the current hospital is created, with an auto-adjusting title that uses `hosp`. +* The plot for the current hospital is temporarily saved and then printed. +* The loop then moves onward to repeat with the next hospital in `hospital_names`. -```{r, out.width='50%', message = F} +```{r, out.width='75%', message = F} # make vector of the hospital names hospital_names <- unique(linelist$hospital) @@ -337,14 +342,6 @@ for (hosp in hospital_names) { fill = "Gender", color = "Gender") - # With older versions of R, remove the # before na_as_group and use this plot command instead. - # plot_hosp <- plot( -# outbreak_hosp, -# fill = "gender", -# color = "black", -# title = stringr::str_glue("Epidemic of cases admitted to {hosp}") -# ) - #print the plot for hospitals print(plot_hosp) @@ -406,7 +403,7 @@ One core **purrr** function is `map()`, which "maps" (applies) a function to eac The basic syntax is `map(.x = SEQUENCE, .f = FUNCTION, OTHER ARGUMENTS)`. In a bit more detail: -* `.x = ` are the *inputs* upon which the `.f` function will be iteratively applied - e.g. a vector of jurisdiction names, columns in a data frame, or a list of data frames +* `.x = ` are the *inputs* upon which the `.f` function will be iteratively applied - e.g. a vector of jurisdiction names, columns in a data frame, or a list of data frames. * `.f = ` is the *function* to apply to each element of the `.x` input - it could be a function like `print()` that already exists, or a custom function that you define. The function is often written after a tilde `~` (details below). A few more notes on syntax: @@ -430,9 +427,9 @@ knitr::include_graphics(here::here("images", "hospital_linelists_excel_sheets.pn Here is one approach that uses `map()`: -1) `map()` the function `import()` so that it runs for each Excel sheet -2) Combine the imported data frames into one using `bind_rows()` -3) Along the way, preserve the original sheet name for each row, storing this information in a new column in the final data frame +1) `map()` the function `import()` so that it runs for each Excel sheet. +2) Combine the imported data frames into one using `bind_rows()`. +3) Along the way, preserve the original sheet name for each row, storing this information in a new column in the final data frame. First, we need to extract the sheet names and save them. We provide the Excel workbook's file path to the function `excel_sheets()` from the package **readxl**, which extracts the sheet names. We store them in a character vector called `sheet_names`. @@ -635,14 +632,14 @@ It is a bit more complex command, but you can also export each hospital-specific Again we use `map()`: we take the vector of list element names (shown above) and use `map()` to iterate through them, applying `export()` (from the **rio** package, see [Import and export](importing.qmd) page) on the data frame in the list `linelist_split` that has that name. We also use the name to create a unique file name. Here is how it works: -* We begin with the vector of character names, passed to `map()` as `.x` -* The `.f` function is `export()` , which requires a data frame and a file path to write to +* We begin with the vector of character names, passed to `map()` as `.x`. +* The `.f` function is `export()` , which requires a data frame and a file path to write to. * The input `.x` (the hospital name) is used *within* `.f` to extract/index that specific element of `linelist_split` list. This results in only one data frame at a time being provided to `export()`. * For example, when `map()` iterates for "Military Hospital", then `linelist_split[[.x]]` is actually `linelist_split[["Military Hospital"]]`, thus returning the second element of `linelist_split` - which is all the cases from Military Hospital. * The file path provided to `export()` is dynamic via use of `str_glue()` (see [Characters and strings](characters_strings.qmd) page): - * `here()` is used to get the base of the file path and specify the "data" folder (note single quotes to not interrupt the `str_glue()` double quotes) -* Then a slash `/`, and then again the `.x` which prints the current hospital name to make the file identifiable -* Finally the extension ".csv" which `export()` uses to create a CSV file + * `here()` is used to get the base of the file path and specify the "data" folder (note single quotes to not interrupt the `str_glue()` double quotes). +* Then a slash `/`, and then again the `.x` which prints the current hospital name to make the file identifiable. +* Finally the extension ".csv" which `export()` uses to create a CSV file. ```{r, eval=F, message = F, warning=F} names(linelist_split) %>% @@ -685,6 +682,24 @@ my_plots <- map( ggarrange(plotlist = my_plots, ncol = 2, nrow = 3) ``` +You can also use `map()` with `ggsave()` to loop through and save plots. + +```{r, echo = T, eval = F} + +map( + .x = hospital_names, + .f = ~ggsave( + filename = here::here( + str_glue("epicurve_{.x}.png")), + ggplot(data = linelist %>% + filter(hospital == .x)) + + geom_histogram(aes(x = date_onset)) + + labs(title = .x) + ) +) + +``` + If this `map()` code looks too messy, you can achieve the same result by saving your specific `ggplot()` command as a custom user-defined function, for example we can name it `make_epicurve())`. This function is then used within the `map()`. `.x` will be iteratively replaced by the hospital name, and used as `hosp_name` in the `make_epicurve()` function. See the page on [Writing functions](writing_functions.qmd). ```{r, eval=F} @@ -716,12 +731,12 @@ Another common use-case is to map a function across many columns. Below, we `map Recall from the page on [Simple statistical tests](stat_tests.qmd) that `t.test()` can take inputs in a formula format, such as `t.test(numeric column ~ binary column)`. In this example, we do the following: -* The numeric columns of interest are selected from `linelist` - these become the `.x` inputs to `map()` -* The function `t.test()` is supplied as the `.f` function, which is applied to each numeric column +* The numeric columns of interest are selected from `linelist` - these become the `.x` inputs to `map()`. +* The function `t.test()` is supplied as the `.f` function, which is applied to each numeric column. * Within the parentheses of `t.test()`: - * the first `~` precedes the `.f` that `map()` will iterate over `.x` - * the `.x` represents the current column being supplied to the function `t.test()` - * the second `~` is part of the t-test equation described above + * the first `~` precedes the `.f` that `map()` will iterate over `.x`. + * the `.x` represents the current column being supplied to the function `t.test()`. + * the second `~` is part of the t-test equation described above. * the `t.test()` function expects a binary column on the right-hand side of the equation. We supply the vector `linelist$gender` independently and statically (note that it is not included in `select()`). `map()` returns a list, so the output is a list of t-test results - one list element for each numeric column analysed. @@ -815,12 +830,12 @@ This is a complex topic - see the Resources section for more complete tutorials. Here are some of the new approaches and functions that will be used: -* The function `tibble()` will be used to create a tibble (like a data frame) - * We surround the `tibble()` function with curly brackets `{ }` to prevent the entire `t.test_results` from being stored as the first tibble column +* The function `tibble()` will be used to create a tibble (like a data frame). + * We surround the `tibble()` function with curly brackets `{ }` to prevent the entire `t.test_results` from being stored as the first tibble column. * Within `tibble()`, each column is created explicitly, similar to the syntax of `mutate()`: - * The `.` represents `t.test_results` - * To create a column with the t-test variable names (the names of each list element) we use `names()` as described above - * To create a column with the p-values we use `map_dbl()` as described above to pull the `p.value` elements and convert them to a numeric vector + * The `.` represents `t.test_results`. + * To create a column with the t-test variable names (the names of each list element) we use `names()` as described above. + * To create a column with the p-values we use `map_dbl()` as described above to pull the `p.value` elements and convert them to a numeric vector. ```{r} t.test_results %>% { @@ -844,9 +859,9 @@ t.test_results %>% Once you have this list column, there are several **tidyr** functions (part of **tidyverse**) that help you "rectangle" or "un-nest" these "nested list" columns. Read more about them [here](), or by running `vignette("rectangle")`. In brief: -* `unnest_wider()` - gives each element of a list-column its own column -* `unnest_longer()` - gives each element of a list-column its own row -* `hoist()` - acts like `unnest_wider()` but you specify which elements to unnest +* `unnest_wider()` - gives each element of a list-column its own column. +* `unnest_longer()` - gives each element of a list-column its own row. +* `hoist()` - acts like `unnest_wider()` but you specify which elements to unnest. Below, we pass the tibble to `unnest_wider()` specifying the tibble's `means` column (which is a nested list). The result is that `means` is replaced by two new columns, each reflecting the two elements that were previously in each `means` cell. @@ -866,10 +881,10 @@ t.test_results %>% Because working with **purrr** so often involves lists, we will briefly explore some **purrr** functions to modify lists. See the Resources section for more complete tutorials on **purrr** functions. -* `list_modify()` has many uses, one of which can be to remove a list element -* `keep()` retains the elements specified to `.p = `, or where a function supplied to `.p = ` evaluates to TRUE -* `discard()` removes the elements specified to `.p`, or where a function supplied to `.p = ` evaluates to TRUE -* `compact()` removes all empty elements +* `list_modify()` has many uses, one of which can be to remove a list element. +* `keep()` retains the elements specified to `.p = `, or where a function supplied to `.p = ` evaluates to TRUE. +* `discard()` removes the elements specified to `.p`, or where a function supplied to `.p = ` evaluates to TRUE. +* `compact()` removes all empty elements. Here are some examples using the `combined` list created in the section above on [using map() to import and combine multiple files](#iter_combined) (it contains 6 case linelist data frames): @@ -916,9 +931,71 @@ combined %>% ### `pmap()` {.unnumbered} -THIS SECTION IS UNDER CONSTRUCTION +The function `pmap()` from the **purrr** package allows us to apply `map_*()` functions over multiple vectors. The "p" in `pmap()` stands for parallel. It works down a a dataset or list sequentially, carrying out your operation. Note, it does **not** refer to parallel [computing](https://bookdown.org/rdpeng/rprogdatascience/parallel-computation.html). + +In `pmap()` you specify a single dataset, or list that contains all of the vectors, or lists, that you want to supply your function. This can allow you to very quickly carry out calculations with multiple columns of a dataframe, or lists of information. + +For example, here is a simple dataset of three numbers. + +```{r, eval = T} + +data_generic <- data.frame( + A = c(1, 10, 100), + B = c(3, 6, 9), + C = c(25, 75, 50) +) + +data_generic + +``` + +Here we are going to using the function `sum()` from **base** R to look at what the sum of each row is. +```{r, eval = T} + +data_generic %>% #Our dataset + pmap_dbl(sum) #The function we want to use + +``` + +You can see that the function `pmap_dbl` has gone through each row of the datasets, and summed the values. While there are other ways of carrying out this operation in this example, such as using `rowSums()` from **base**, `pmap_*()` functions are much quicker. Additionally, `pmap_*()` allows you to input custom functions, and specify more complicated inputs. + +For example, here we are going to create a new column to count how many symptoms those in our `linelist` dataset have. + +```{r, eval = T} + +linelist_symptom_count <- linelist %>% #Our dataset + mutate(number_symptoms = linelist %>% #Creating a new column to count symptoms + select(fever:vomit) %>% #Selecting the columns that indicate the presence of symptoms + pmap_int(~sum(c(...) == "yes", na.rm = T))) #Here pmap is looking at each row of symptoms, counting which values are set as "yes" and then summing all the values in the row + +#Display the results +linelist_symptom_count %>% + select(fever:vomit, number_symptoms) %>% + slice(1:10) + + +``` + +As another example, here we have written our own [custom function](writing_functions.qmd), using `str_glue`, see section on [Characters and strings](characters_strings.qmd) to summarise each patient's gender, age, date of onset, outcome and the date of outcome: + +```{r, eval = T} + +#Function +summarise_function <- function(case_id, gender, age, date_onset, date_outcome, outcome, ...){ + str_glue("Case {case_id} who had the gender {gender} and the age {age}, had symptom onset on {date_onset}, and had the outcome of {outcome} on {date_outcome}.") +} + +#Run the custom pmap function +linelist_summary <- linelist %>% + pmap_chr(summarise_function) + +#Display only the first 2 for ease of viewing +linelist_summary[1:2] + +``` +Note that here we did not even have to specify which columns to use, as they are the same name in the function, `summarise_function()` as in the dataset. `pmap_*()` functions automatically map the column or list names to the function. ## Apply functions diff --git a/new_pages/joining_matching.qmd b/new_pages/joining_matching.qmd index c3825089..927e3abf 100644 --- a/new_pages/joining_matching.qmd +++ b/new_pages/joining_matching.qmd @@ -10,13 +10,13 @@ knitr::include_graphics(here::here("images", "left-join.gif")) This page describes ways to "join", "match", "link" "bind", and otherwise combine data frames. -It is uncommon that your epidemiological analysis or workflow does not involve multiple sources of data, and the linkage of multiple datasets. Perhaps you need to connect laboratory data to patient clinical outcomes, or Google mobility data to infectious disease trends, or even a dataset at one stage of analysis to a transformed version of itself. +It is common that your epidemiological analysis or workflow does involves multiple sources of data, and the linkage of multiple datasets. Perhaps you need to connect laboratory data to patient clinical outcomes, or Google mobility data to infectious disease trends, or even a dataset at one stage of analysis to a transformed version of itself. In this page we demonstrate code to: -* Conduct *joins* of two data frames such that rows are matched based on common values in identifier columns -* Join two data frames based on *probabilistic* (likely) matches between values -* Expand a data frame by directly *binding* or ("appending") rows or columns from another data frame +* Conduct *joins* of two data frames such that rows are matched based on common values in identifier columns. +* Join two data frames based on *probabilistic* (likely) matches between values. +* Expand a data frame by directly *binding* or ("appending") rows or columns from another data frame. @@ -42,7 +42,7 @@ pacman::p_load( To begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning = F, message = F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -67,8 +67,8 @@ DT::datatable(head(linelist, 50), rownames = FALSE, filter="top", options = list In the joining section below, we will use the following datasets: -1) A "miniature" version of the case `linelist`, containing only the columns `case_id`, `date_onset`, and `hospital`, and only the first 10 rows -2) A separate data frame named `hosp_info`, which contains more details about each hospital +1) A "miniature" version of the case `linelist`, containing only the columns `case_id`, `date_onset`, and `hospital`, and only the first 10 rows. +2) A separate data frame named `hosp_info`, which contains more details about each hospital. In the section on probabilistic matching, we will use two different small datasets. The code to create those datasets is given in that section. @@ -226,7 +226,7 @@ df1 <- df1 %>% ``` -**_CAUTION:_** Joins are case-specific! Therefore it is useful to convert all values to lowercase or uppercase prior to joining. See the page on characters/strings. +**_CAUTION:_** Joins are case-specific! Therefore it is useful to convert all values to lowercase or uppercase prior to joining. See the page on [characters/strings](characters_strings). @@ -237,16 +237,16 @@ df1 <- df1 %>% **A left or right join is commonly used to add information to a data frame** - new information is added only to rows that already existed in the baseline data frame. These are common joins in epidemiological work as they are used to add information from one dataset into another. -In using these joins, the written order of the data frames in the command is important*. +In using these joins, *the written order of the data frames in the command is important*. -* In a *left join*, the *first* data frame written is the baseline -* In a *right join*, the *second* data frame written is the baseline +* In a *left join*, the *first* data frame written is the baseline. +* In a *right join*, the *second* data frame written is the baseline. **All rows of the baseline data frame are kept.** Information in the other (secondary) data frame is joined to the baseline data frame *only if there is a match via the identifier column(s)*. In addition: * Rows in the secondary data frame that do not match are dropped. * If there are many baseline rows that match to one row in the secondary data frame (many-to-one), the secondary information is added to *each matching baseline row*. -* If a baseline row matches to multiple rows in the secondary data frame (one-to-many), all combinations are given, meaning *new rows may be added to your returned data frame!* +* If a baseline row matches to multiple rows in the secondary data frame (one-to-many), all combinations are given, meaning *new rows may be added to your returned data frame!*. Animated examples of left and right joins ([image source](https://github.com/gadenbuie/tidyexplain/tree/master/images)) @@ -259,12 +259,12 @@ knitr::include_graphics(here::here("images", "right-join.gif")) Below is the output of a `left_join()` of `hosp_info` (secondary data frame, [view here](#joins_hosp_info)) *into* `linelist_mini` (baseline data frame, [view here](#joins_llmini)). The original `linelist_mini` has ` nrow(linelist_mini)` rows. The modified `linelist_mini` is displayed. Note the following: -* Two new columns, `catchment_pop` and `level` have been added on the left side of `linelist_mini` -* All original rows of the baseline data frame `linelist_mini` are kept -* Any original rows of `linelist_mini` for "Military Hospital" are duplicated because it matched to *two* rows in the secondary data frame, so both combinations are returned -* The join identifier column of the secondary dataset (`hosp_name`) has disappeared because it is redundant with the identifier column in the primary dataset (`hospital`) -* When a baseline row did not match to any secondary row (e.g. when `hospital` is "Other" or "Missing"), `NA` (blank) fills in the columns from the secondary data frame -* Rows in the secondary data frame with no match to the baseline data frame ("sisters" and "ignace" hospitals) were dropped +* Two new columns, `catchment_pop` and `level` have been added on the left side of `linelist_mini`. +* All original rows of the baseline data frame `linelist_mini` are kept. +* Any original rows of `linelist_mini` for "Military Hospital" are duplicated because it matched to *two* rows in the secondary data frame, so both combinations are returned. +* The join identifier column of the secondary dataset (`hosp_name`) has disappeared because it is redundant with the identifier column in the primary dataset (`hospital`). +* When a baseline row did not match to any secondary row (e.g. when `hospital` is "Other" or "Missing"), `NA` (blank) fills in the columns from the secondary data frame. +* Rows in the secondary data frame with no match to the baseline data frame ("sisters" and "ignace" hospitals) were dropped. ```{r, eval=F} @@ -330,11 +330,11 @@ Animated example of a full join ([image source](https://github.com/gadenbuie/tid Below is the output of a `full_join()` of `hosp_info` (originally ` nrow(hosp_info)`, [view here](#joins_hosp_info)) *into* `linelist_mini` (originally ` nrow(linelist_mini)`, [view here](#joins_llmini)). Note the following: -* All baseline rows are kept (`linelist_mini`) -* Rows in the secondary that do not match to the baseline are kept ("ignace" and "sisters"), with values in the corresponding baseline columns `case_id` and `onset` filled in with missing values -* Likewise, rows in the baseline data frame that do not match to the secondary ("Other" and "Missing") are kept, with secondary columns ` catchment_pop` and `level` filled-in with missing values -* In the case of one-to-many or many-to-one matches (e.g. rows for "Military Hospital"), all possible combinations are returned (lengthening the final data frame) -* Only the identifier column from the baseline is kept (`hospital`) +* All baseline rows are kept (`linelist_mini`). +* Rows in the secondary that do not match to the baseline are kept ("ignace" and "sisters"), with values in the corresponding baseline columns `case_id` and `onset` filled in with missing values. +* Likewise, rows in the baseline data frame that do not match to the secondary ("Other" and "Missing") are kept, with secondary columns ` catchment_pop` and `level` filled-in with missing values. +* In the case of one-to-many or many-to-one matches (e.g. rows for "Military Hospital"), all possible combinations are returned (lengthening the final data frame). +* Only the identifier column from the baseline is kept (`hospital`). ```{r, eval=F} @@ -355,7 +355,8 @@ linelist_mini %>% ### Inner join {.unnumbered} -**An inner join is the most *restrictive* of the joins** - it returns only rows with matches across both data frames. +**An inner join is the most *restrictive* of the joins** - it returns only rows with matches across both data frames. + This means that the number of rows in the baseline data frame may actually *reduce*. Adjustment of which data frame is the "baseline" (written first in the function) will not impact which rows are returned, but it will impact the column order, row order, and which identifier columns are retained. @@ -370,9 +371,9 @@ Animated example of an inner join ([image source](https://github.com/gadenbuie/t Below is the output of an `inner_join()` of `linelist_mini` (baseline) with `hosp_info` (secondary). Note the following: -* Baseline rows with no match to the secondary data are removed (rows where `hospital` is "Missing" or "Other") -* Likewise, rows from the secondary data frame that had no match in the baseline are removed (rows where `hosp_name` is "sisters" or "ignace") -* Only the identifier column from the baseline is kept (`hospital`) +* Baseline rows with no match to the secondary data are removed (rows where `hospital` is "Missing" or "Other"). +* Likewise, rows from the secondary data frame that had no match in the baseline are removed (rows where `hosp_name` is "sisters" or "ignace"). +* Only the identifier column from the baseline is kept (`hospital`). ```{r, eval=F} @@ -558,14 +559,14 @@ DT::datatable(results, rownames = FALSE, options = list(pageLength = nrow(result The `fastLink()` function from the **fastLink** package can be used to apply a matching algorithm. Here is the basic information. You can read more detail by entering `?fastLink` in your console. -* Define the two data frames for comparison to arguments `dfA = ` and `dfB = ` +* Define the two data frames for comparison to arguments `dfA = ` and `dfB = `. * In `varnames = ` give all column names to be used for matching. They must all exist in both `dfA` and `dfB`. * In `stringdist.match = ` give columns from those in `varnames` to be evaluated on string "distance". * In `numeric.match = ` give columns from those in `varnames` to be evaluated on numeric distance. -* Missing values are ignored +* Missing values are ignored. * By default, each row in either data frame is matched to at most one row in the other data frame. If you want to see all the evaluated matches, set `dedupe.matches = FALSE`. The deduplication is done using Winkler's linear assignment solution. -*Tip: split one date column into three separate numeric columns using `day()`, `month()`, and `year()` from **lubridate** package* +*Tip: split one date column into three separate numeric columns using `day()`, `month()`, and `year()` from **lubridate** package*. The default threshold for matches is 0.94 (`threshold.match = `) but you can adjust it higher or lower. If you define the threshold, consider that higher thresholds could yield more false-negatives (rows that do not match which actually should match) and likewise a lower threshold could yield more false-positive matches. @@ -610,13 +611,13 @@ Things to note: To use these matches to join `results` to `cases`, one strategy is: -1) Use `left_join()` to join `my_matches` to `cases` (matching rownames in `cases` to "inds.a" in `my_matches`) -2) Then use another `left_join()` to join `results` to `cases` (matching the newly-acquired "inds.b" in `cases` to rownames in `results`) +1) Use `left_join()` to join `my_matches` to `cases` (matching rownames in `cases` to "inds.a" in `my_matches`). +2) Then use another `left_join()` to join `results` to `cases` (matching the newly-acquired "inds.b" in `cases` to rownames in `results`). Before the joins, we should clean the three data frames: * Both `dfA` and `dfB` should have their row numbers ("rowname") converted to a proper column. -* Both the columns in `my_matches` are converted to class character, so they can be joined to the character rownames +* Both the columns in `my_matches` are converted to class character, so they can be joined to the character rownames. ```{r} # Clean data prior to joining @@ -909,17 +910,17 @@ A **base** R alternative to `bind_cols` is `cbind()`, which performs the same op ## Resources { } -The [tidyverse page on joins](https://dplyr.tidyverse.org/reference/join.html) +The [tidyverse page on joins](https://dplyr.tidyverse.org/reference/mutate-joins.html). -The [R for Data Science page on relational data](https://r4ds.had.co.nz/relational-data.html) +The [R for Data Science page on relational data](https://r4ds.had.co.nz/relational-data.html). -Th [tidyverse page on dplyr](https://dplyr.tidyverse.org/reference/bind.html) on binding +Th [tidyverse page on dplyr](https://dplyr.tidyverse.org/reference/bind.html) on binding. -A vignette on [fastLink](https://github.com/kosukeimai/fastLink) at the package's Github page +A vignette on [fastLink](https://github.com/kosukeimai/fastLink) at the package's Github page. -Publication describing methodology of [fastLink](https://imai.fas.harvard.edu/research/files/linkage.pdf) +Publication describing methodology of [fastLink](https://imai.fas.harvard.edu/research/files/linkage.pdf). -Publication describing [RecordLinkage package](https://journal.r-project.org/archive/2010/RJ-2010-017/RJ-2010-017.pdf) +Publication describing [RecordLinkage package](https://journal.r-project.org/archive/2010/RJ-2010-017/RJ-2010-017.pdf). diff --git a/new_pages/missing_data.qmd b/new_pages/missing_data.qmd index 1559e80e..421d68e8 100644 --- a/new_pages/missing_data.qmd +++ b/new_pages/missing_data.qmd @@ -37,7 +37,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, message=F, warning=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -125,7 +125,7 @@ Impossible values are represented by the special value `NaN`. An example of this `Inf` represents an infinite value, such as when you divide a number by 0. -As an example of how this might impact your work: let's say you have a vector/column `z` that contains these values: `z <- c(1, 22, NA, Inf, NaN, 5)` +As an example of how this might impact your work: let's say you have a vector/column `z` that contains these values: `z <- c(1, 22, NA, Inf, NaN, 5)`. If you want to use `max()` on the column to find the highest value, you can use the `na.rm = TRUE` to remove the `NA` from the calculation, but the `Inf` and `NaN` remain and `Inf` will be returned. To resolve this, you can use brackets `[ ]` and `is.finite()` to subset such that only finite values are used for the calculation: `max(z[is.finite(z)])`. @@ -266,8 +266,8 @@ pct_complete_case(linelist) # use n_complete() for counts The `gg_miss_var()` function will show you the number (or %) of missing values in each column. A few nuances: * You can add a column name (not in quote) to the argument `facet = ` to see the plot by groups -* By default, counts are shown instead of percents, change this with `show_pct = TRUE` -* You can add axis and title labels as for a normal `ggplot()` with `+ labs(...)` +* By default, counts are shown instead of percents, change this with `show_pct = TRUE` +* You can add axis and title labels as for a normal `ggplot()` with `+ labs(...)` ```{r} @@ -292,7 +292,7 @@ vis_miss(linelist) ### Explore and visualize missingness relationships {.unnumbered} -How do you visualize something that is not there??? By default, `ggplot()` removes points with missing values from plots. +How do you visualize something that is not there? By default, `ggplot()` removes points with missing values from plots. **naniar** offers a solution via `geom_miss_point()`. When creating a scatterplot of two columns, records with one of the values missing and the other value present are shown by setting the missing values to 10% lower than the lowest value in the column, and coloring them distinctly. @@ -303,7 +303,8 @@ In the scatterplot below, the red dots are records where the value for one colum ```{r} ggplot( data = linelist, - mapping = aes(x = age_years, y = temp)) + + mapping = aes(x = age_years, y = temp) + ) + geom_miss_point() ``` @@ -366,8 +367,8 @@ linelist %>% An alternative way to plot the proportion of a column's values that are missing over time is shown below. It does *not* involve **naniar**. This example shows percent of weekly observations that are missing). -1) Aggregate the data into a useful time unit (days, weeks, etc.), summarizing the proportion of observations with `NA` (and any other values of interest) -2) Plot the proportion missing as a line using `ggplot()` +1) Aggregate the data into a useful time unit (days, weeks, etc.), summarizing the proportion of observations with `NA` (and any other values of interest) +2) Plot the proportion missing as a line using `ggplot()` Below, we take the linelist, add a new column for week, group the data by week, and then calculate the percent of that week's records where the value is missing. (note: if you want % of 7 days the calculation would be slightly different). @@ -452,8 +453,8 @@ It is often wise to report the number of values excluded from a plot in a captio In `ggplot()`, you can add `labs()` and within it a `caption = `. In the caption, you can use `str_glue()` from **stringr** package to paste values together into a sentence dynamically so they will adjust to the data. An example is below: -* Note the use of `\n` for a new line. -* Note that if multiple column would contribute to values not being plotted (e.g. age or sex if those are reflected in the plot), then you must filter on those columns as well to correctly calculate the number not shown. +* Note the use of `\n` for a new line +* Note that if multiple column would contribute to values not being plotted (e.g. age or sex if those are reflected in the plot), then you must filter on those columns as well to correctly calculate the number not shown ```{r, eval=F} labs( @@ -462,7 +463,8 @@ labs( x = "", caption = stringr::str_glue( "n = {nrow(central_data)} from Central Hospital; - {nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown.")) + {nrow(central_data %>% filter(is.na(date_onset)))} cases missing date of onset and not shown.") + ) ``` Sometimes, it can be easier to save the string as an object in commands prior to the `ggplot()` command, and simply reference the named string object within the `str_glue()`. @@ -500,9 +502,36 @@ It's important to think about why your data might be missing in addition to seei Here are three general types of missing data: -1) **Missing Completely at Random** (MCAR). This means that there is no relationship between the probability of data being missing and any of the other variables in your data. The probability of being missing is the same for all cases This is a rare situation. But, if you have strong reason to believe your data is MCAR analyzing only non-missing data without imputing won't bias your results (although you may lose some power). [TODO: consider discussing statistical tests for MCAR] +1) **Missing Completely at Random** (MCAR). This means that there is no relationship between the probability of data being missing and any of the other variables in your data. The probability of being missing is the same for all cases This is a rare situation. But, if you have strong reason to believe your data is MCAR analyzing only non-missing data without imputing won't bias your results (although you may lose some power). -2) **Missing at Random** (MAR). This name is actually a bit misleading as MAR means that your data is missing in a systematic, predictable way based on the other information you have. For example, maybe every observation in our dataset with a missing value for fever was actually not recorded because every patient with chills and and aches was just assumed to have a fever so their temperature was never taken. If true, we could easily predict that every missing observation with chills and aches has a fever as well and use this information to impute our missing data. In practice, this is more of a spectrum. Maybe if a patient had both chills and aches they were more likely to have a fever as well if they didn't have their temperature taken, but not always. This is still predictable even if it isn't perfectly predictable. This is a common type of missing data +To test if your data is MCAR, you can use `mcar_test` from the **naniar** package. **Note** this will only work with numerical values, so you may need to convert your data before running the test. + +If the p-value is less than or equal to 0.05, then your data is **not** missing completely at random. + +```{r} + +## define variables of interest +explanatory_vars <- c("gender", "fever", "chills", "cough", "aches", "vomit") + +#Recode linelist to replace characters with numeric +linelist <- linelist %>% + mutate(across( + .cols = all_of(c(explanatory_vars, "outcome")), # for each column listed and "outcome" + .fns = ~case_when( + . %in% c("m", "yes", "Death") ~ 1, # recode male, yes and death to 1 + . %in% c("f", "no", "Recover") ~ 0, # female, no and recover to 0 + TRUE ~ NA_real_) # otherwise set to missing + ) + ) + +linelist %>% + select(explanatory_vars, "outcome") %>% + mcar_test() + +``` +As the p-value is **greater** than 0.05, we have failed to reject the null hypothesis (that the data is MCAR), and so no patterns exist in the missing data. + +2) **Missing at Random** (MAR). This name is actually a bit misleading as MAR means that your data is missing in a systematic, predictable way based on the other information you have. For example, maybe every observation in our dataset with a missing value for fever was actually not recorded because every patient with chills and and aches was just assumed to have a fever so their temperature was never taken. If true, we could easily predict that every missing observation with chills and aches has a fever as well and use this information to impute our missing data. In practice, this is more of a spectrum. Maybe if a patient had both chills and aches they were more likely to have a fever as well if they didn't have their temperature taken, but not always. This is still predictable even if it isn't perfectly predictable. This is a common type of missing data. 3) **Missing not at Random** (MNAR). Sometimes, this is also called **Not Missing at Random** (NMAR). This assumes that the probability of a value being missing is NOT systematic or predictable using the other information we have but also isn't missing randomly. In this situation data is missing for unknown reasons or for reasons you don't have any information about. For example, in our dataset maybe information on age is missing because some very elderly patients either don't know or refuse to say how old they are. In this situation, missing data on age is related to the value itself (and thus isn't random) and isn't predictable based on the other information we have. MNAR is complex and often the best way of dealing with this is to try to collect more data or information about why the data is missing rather than attempt to impute it. @@ -510,7 +539,7 @@ In general, imputing MCAR data is often fairly simple, while MNAR is very challe ### Useful packages {.unnumbered} -Some useful packages for imputing missing data are Mmisc, missForest (which uses random forests to impute missing data), and mice (Multivariate Imputation by Chained Equations). For this section we'll just use the mice package, which implements a variety of techniques. The maintainer of the mice package has published an online book about imputing missing data that goes into more detail here (https://stefvanbuuren.name/fimd/). +Some useful packages for imputing missing data are **Mmisc**, **missForest** (which uses random forests to impute missing data), and **mice** (Multivariate Imputation by Chained Equations). For this section we'll just use the mice package, which implements a variety of techniques. The maintainer of the mice package has published an online book about imputing missing data that goes into more detail here (https://stefvanbuuren.name/fimd/). Here is the code to load the mice package: @@ -527,11 +556,11 @@ linelist <- linelist %>% mutate(temp_replace_na_with_mean = replace_na(temp, mean(temp, na.rm = T))) ``` -You could also do a similar process for replacing categorical data with a specific value. For our dataset, imagine you knew that all observations with a missing value for their outcome (which can be "Death" or "Recover") were actually people that died (note: this is not actually true for this dataset): +You could also do a similar process for replacing categorical data with a specific value. For our dataset, imagine you knew that all observations with a missing value for their outcome (which can be "Death" or "Recover") were actually people that died (**note: this is not actually true for this dataset**): ```{r} linelist <- linelist %>% - mutate(outcome_replace_na_with_death = replace_na(outcome, "Death")) + mutate(outcome_replace_na_with_death = replace_na(outcome, 1)) ``` ### Regression imputation {.unnumbered} @@ -608,18 +637,20 @@ disease <- tibble::tribble( disease %>% fill(year, .direction = "up") ``` -In this example, LOCF and BOCF are clearly the right things to do, but in more complicated situations it may be harder to decide if these methods are appropriate. For example, you may have missing laboratory values for a hospital patient after the first day. Sometimes, this can mean the lab values didn't change...but it could also mean the patient recovered and their values would be very different after the first day! Use these methods with caution. +In this example, LOCF and BOCF are clearly the right things to do, but in more complicated situations it may be harder to decide if these methods are appropriate. For example, you may have missing laboratory values for a hospital patient after the first day. Sometimes, this can mean the lab values didn't change...but it could also mean the patient recovered and their values would be very different after the first day! + +**Use these methods with caution.** ### Multiple Imputation {.unnumbered} -The online book we mentioned earlier by the author of the mice package (https://stefvanbuuren.name/fimd/) contains a detailed explanation of multiple imputation and why you'd want to use it. But, here is a basic explanation of the method: +The online book we mentioned earlier by the author of the **mice** package ([https://stefvanbuuren.name/fimd/](https://stefvanbuuren.name/fimd)) contains a detailed explanation of multiple imputation and why you'd want to use it. But, here is a basic explanation of the method: -When you do multiple imputation, you create multiple datasets with the missing values imputed to plausible data values (depending on your research data you might want to create more or less of these imputed datasets, but the mice package sets the default number to 5). The difference is that rather than a single, specific value each imputed value is drawn from an estimated distribution (so it includes some randomness). As a result, each of these datasets will have slightly different different imputed values (however, the non-missing data will be the same in each of these imputed datasets). You still use some sort of predictive model to do the imputation in each of these new datasets (mice has many options for prediction methods including *Predictive Mean Matching*, *logistic regression*, and *random forest*) but the mice package can take care of many of the modeling details. +When you do multiple imputation, you create multiple datasets with the missing values imputed to plausible data values (depending on your research data you might want to create more or less of these imputed datasets, but the **mice** package sets the default number to 5). The difference is that rather than a single, specific value each imputed value is drawn from an estimated distribution (so it includes some randomness). As a result, each of these datasets will have slightly different different imputed values (however, the non-missing data will be the same in each of these imputed datasets). You still use some sort of predictive model to do the imputation in each of these new datasets (**mice** has many options for prediction methods including *Predictive Mean Matching*, *logistic regression*, and *random forest*) but the **mice** package can take care of many of the modeling details. Then, once you have created these new imputed datasets, you can apply then apply whatever statistical model or analysis you were planning to do for each of these new imputed datasets and pool the results of these models together. This works very well to reduce bias in both MCAR and many MAR settings and often results in more accurate standard error estimates. -Here is an example of applying the Multiple Imputation process to predict temperature in our linelist dataset using a age and fever status (our simplified model_dataset from above): +Here is an example of applying the Multiple Imputation process to predict temperature in our linelist dataset using a age and fever status (our simplified `model_dataset` from above): ```{r} # imputing missing values for all variables in our model_dataset, and creating 10 new imputed datasets @@ -627,14 +658,15 @@ multiple_imputation = mice( model_dataset, seed = 1, m = 10, - print = FALSE) + print = FALSE + ) model_fit <- with(multiple_imputation, lm(temp ~ age_years + fever)) base::summary(mice::pool(model_fit)) ``` -Here we used the mice default method of imputation, which is Predictive Mean Matching. We then used these imputed datasets to separately estimate and then pool results from simple linear regressions on each of these datasets. There are many details we've glossed over and many settings you can adjust during the Multiple Imputation process while using the mice package. For example, you won't always have numerical data and might need to use other imputation methods (you can still use the mice package for many other types of data and methods). But, for a more robust analysis when missing data is a significant concern, Multiple Imputation is good solution that isn't always much more work than doing a complete case analysis. +Here we used the **mice** default method of imputation, which is Predictive Mean Matching. We then used these imputed datasets to separately estimate and then pool results from simple linear regressions on each of these datasets. There are many details we've glossed over and many settings you can adjust during the Multiple Imputation process while using the **mice** package. For example, you won't always have numerical data and might need to use other imputation methods (you can still use the **mice** package for many other types of data and methods). But, for a more robust analysis when missing data is a significant concern, Multiple Imputation is good solution that isn't always much more work than doing a complete case analysis. diff --git a/new_pages/moving_average.qmd b/new_pages/moving_average.qmd index 043567fd..32f2151a 100644 --- a/new_pages/moving_average.qmd +++ b/new_pages/moving_average.qmd @@ -8,8 +8,8 @@ knitr::include_graphics(here::here("images", "moving_avg_epicurve.png")) This page will cover two methods to calculate and visualize moving averages: -1) Calculate with the **slider** package -2) Calculate *within* a `ggplot()` command with the **tidyquant** package +1) Calculate with the **slider** package +2) Calculate *within* a `ggplot()` command with the **tidyquant** package @@ -35,7 +35,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, message=F, warning=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -73,13 +73,13 @@ The **slider** package has many other functions that are covered in the Resource **Core arguments** -* `.x`, the first argument by default, is the vector to iterate over and to apply the function to -* `.i = ` for the "index" versions of the **slider** functions - provide a column to "index" the roll on (see section [below](#roll_index)) -* `.f = `, the second argument by default, either: - * A function, written without parentheses, like `mean`, or - * A formula, which will be converted into a function. For example `~ .x - mean(.x)` will return the result of the current value minus the mean of the window's value +* `x`, the first argument by default, is the vector to iterate over and to apply the function to +* `i = ` for the "index" versions of the **slider** functions - provide a column to "index" the roll on (see section [below](#roll_index)) +* `f = `, the second argument by default, either: + * A function, written without parentheses, like `mean`, or, + * A formula, which will be converted into a function For example `~ x - mean(x)` will return the result of the current value minus the mean of the window's value -* For more details see this [reference material](https://davisvaughan.github.io/slider/reference/slide.html) +* For more details see this [reference material](https://cran.r-project.org/web/packages/slider/vignettes/slider.html) @@ -87,9 +87,9 @@ The **slider** package has many other functions that are covered in the Resource Specify the size of the window by using either `.before`, `.after`, or both arguments: -* `.before = ` - Provide an integer -* `.after = ` - Provide an integer -* `.complete = ` - Set this to `TRUE` if you only want calculation performed on complete windows +* `before = ` - Provide an integer +* `after = ` - Provide an integer +* `complete = ` - Set this to `TRUE` if you only want calculation performed on complete windows For example, to achieve a 7-day window including the current value and the six previous, use `.before = 6`. To achieve a "centered" window provide the same number to both `.before = ` and `.after = `. @@ -175,8 +175,8 @@ DT::datatable(rolling, rownames = FALSE, options = list(pageLength = 12, scrollX Now you can plot these data using `ggplot()`: ```{r} -ggplot(data = rolling)+ - geom_line(mapping = aes(x = date_hospitalisation, y = indexed_7day), size = 1) +ggplot(data = rolling) + + geom_line(mapping = aes(x = date_hospitalisation, y = indexed_7day), linewidth = 1) ``` @@ -253,22 +253,22 @@ We can now plot the moving averages, displaying the data by group by specifying ```{r, warning=F, message=F} -ggplot(data = grouped_roll)+ +ggplot(data = grouped_roll) + geom_col( # plot daly case counts as grey bars mapping = aes( x = date_hospitalisation, y = new_cases), fill = "grey", - width = 1)+ + width = 1) + geom_line( # plot rolling average as line colored by hospital mapping = aes( x = date_hospitalisation, y = mean_7day_hosp, color = hospital), - size = 1)+ - facet_wrap(~hospital, ncol = 2)+ # create mini-plots per hospital - theme_classic()+ # simplify background - theme(legend.position = "none")+ # remove legend + size = 1) + + facet_wrap(~hospital, ncol = 2) + # create mini-plots per hospital + theme_classic() + # simplify background + theme(legend.position = "none") + # remove legend labs( # add plot labels title = "7-day rolling average of daily case incidence", x = "Date of admission", @@ -311,7 +311,7 @@ ggplot(data = grouped_roll)+ - + @@ -330,23 +330,23 @@ Below the `linelist` data are counted by date of onset, and this is plotted as a By default `geom_ma()` uses a simple moving average (`ma_fun = "SMA"`), but other types can be specified, such as: -* "EMA" - exponential moving average (more weight to recent observations) -* "WMA" - weighted moving average (`wts` are used to weight observations in the moving average) -* Others can be found in the function documentation +* "EMA" - exponential moving average (more weight to recent observations) +* "WMA" - weighted moving average (`wts` are used to weight observations in the moving average) +* Others can be found in the function documentation ```{r} linelist %>% count(date_onset) %>% # count cases per day drop_na(date_onset) %>% # remove cases missing onset date - ggplot(aes(x = date_onset, y = n))+ # start ggplot + ggplot(aes(x = date_onset, y = n)) + # start ggplot geom_line( # plot raw values size = 1, alpha = 0.2 # semi-transparent line - )+ + ) + tidyquant::geom_ma( # plot moving average n = 7, size = 1, - color = "blue")+ + color = "blue") + theme_minimal() # simple background ``` @@ -362,7 +362,7 @@ See this [vignette](https://cran.r-project.org/web/packages/tidyquant/vignettes/ - + @@ -384,7 +384,7 @@ See the helpful online [vignette for the **slider** package](https://cran.r-proj The **slider** [github page](https://github.com/DavisVaughan/slider) -A **slider** [vignette](https://davisvaughan.github.io/slider/articles/slider.html) +A **slider** [vignette](https://cran.r-project.org/web/packages/slider/vignettes/slider.html) [tidyquant vignette](https://cran.r-project.org/web/packages/tidyquant/vignettes/TQ04-charting-with-tidyquant.html) diff --git a/new_pages/network.html b/new_pages/network.html new file mode 100644 index 00000000..b3d9a8b8 --- /dev/null +++ b/new_pages/network.html @@ -0,0 +1,5319 @@ + + + + +visNetwork + + + + +

    + + + +
    +
    +
    + + + + diff --git a/new_pages/network_drives.qmd b/new_pages/network_drives.qmd index aa73781e..d8588852 100644 --- a/new_pages/network_drives.qmd +++ b/new_pages/network_drives.qmd @@ -88,17 +88,17 @@ For example, `Error in tools::startDynamicHelp() : internet routines cannot be l * Try selecting 32-bit version from RStudio via Tools/Global Options. * note: if 32-bit version does not appear in menu, make sure you are not using RStudio v1.2. -* Alternatively, try uninstalling R and re-installing with different bit version (32 instead of 64) +* Alternatively, try uninstalling R and re-installing with different bit version (32 instead of 64). **C: library does not appear as an option when I try to install packages manually** * Run RStudio as an administrator, then this option will appear. -* To set-up RStudio to always run as administrator (advantageous when using an Rproject where you don't click RStudio icon to open)... right-click the Rstudio icon +* To set-up RStudio to always run as administrator (advantageous when using an Rproject where you don't click RStudio icon to open), right-click the Rstudio icon. The image below shows how you can manually select the library to install a package to. This window appears when you open the Packages RStudio pane and click "Install". -```{r, warning=F, message=F, echo=F} +```{r, warning=F, out.height='75%', out.width='75%', message=F, echo=F} knitr::include_graphics(here::here("images", "network_install.png")) ``` @@ -106,20 +106,20 @@ knitr::include_graphics(here::here("images", "network_install.png")) If you are getting "pandoc error 1" when knitting R Markdowns scripts on network drives: -* Of multiple library locations, have the one with a lettered drive listed first (see codes above) -* The above solution worked when knitting on local drive but while on a networked internet connection -* See more tips here: https://ciser.cornell.edu/rmarkdown-knit-to-html-word-pdf/ +* Of multiple library locations, have the one with a lettered drive listed first (see codes above). +* The above solution worked when knitting on local drive but while on a networked internet connection. +* See more tips [here](https://forum.posit.co/t/error-when-knitting-rmarkdown-to-html-on-a-networked-drive/72459). **Pandoc Error 83** The error will look something like this: `can't find file...rmarkdown...lua...`. This means that it was unable to find this file. -See https://stackoverflow.com/questions/58830927/rmarkdown-unable-to-locate-lua-filter-when-knitting-to-word +See [here](https://stackoverflow.com/questions/58830927/rmarkdown-unable-to-locate-lua-filter-when-knitting-to-word). Possibilities: -1) Rmarkdown package is not installed -2) Rmarkdown package is not findable +1) Rmarkdown package is not installed. +2) Rmarkdown package is not findable. 3) An admin rights issue. It is possible that R is not able to find the **rmarkdown** package file, so check which library the **rmarkdown** package lives (see code above). If the package is installed to a library that in inaccessible (e.g. starts with "\\\") consider manually moving it to C: or other named drive library. Be aware that the **rmarkdown** package has to be able to connect to TinyTex installation, so can not live in a library on a network drive. @@ -129,14 +129,14 @@ It is possible that R is not able to find the **rmarkdown** package file, so che For example: `Error: pandoc document conversion failed with error 61` or `Could not fetch...` -* Try running RStudio as administrator (right click icon, select run as admin, see above instructions) +* Try running RStudio as administrator (right click icon, select run as admin, see above instructions). * Also see if the specific package that was unable to be reached can be moved to C: library. **LaTex error (see below)** An error like: `! Package pdftex.def Error: File 'cict_qm2_2020-06-29_files/figure-latex/unnamed-chunk-5-1.png' not found: using draft setting.` or `Error: LaTeX failed to compile file_name.tex.` -* See https://yihui.org/tinytex/r/#debugging for debugging tips. +* See this [article](https://yihui.org/tinytex/r/#debugging) for debugging tips. * See file_name.log for more info. diff --git a/new_pages/packages_suggested.html b/new_pages/packages_suggested.html new file mode 100644 index 00000000..03ccaf0a --- /dev/null +++ b/new_pages/packages_suggested.html @@ -0,0 +1,1489 @@ + + + + + + + + + +The Epidemiologist R Handbook - 5  Suggested packages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + +
    +
    + +
    + +
    + + +
    + + + +
    + +
    +
    +

    5  Suggested packages

    +
    + + + +
    + + + + +
    + + + +
    + + +

    Below is a long list of suggested packages for common epidemiological work in R. You can copy this code, run it, and all of these packages will install from CRAN and load for use in the current R session. If a package is already installed, it will be loaded for use only.

    +

    You can modify the code with # symbols to exclude any packages you do not want.

    +

    Of note:

    +
      +
    • Install the pacman package first before running the below code. You can do this with install.packages("pacman"). In this handbook we emphasize p_load() from pacman, which installs the package if necessary and loads it for use in the current R session. You can also load packages that are already installed with library() from base R.
      +
    • +
    • In the code below, packages that are included when installing/loading another package are indicated by an indent and hash. For example how ggplot2 is listed under tidyverse.
      +
    • +
    • If multiple packages have functions with the same name, masking can occur when the function from the more recently-loaded package takes precedent. Read more in the R basics page. Consider using the package conflicted to manage such conflicts.
      +
    • +
    • See the R basics section on packages for more information on pacman and masking.
    • +
    +

    To see the versions of R, RStudio, and R packages used during the production of this handbook, see the page on Editorial and technical notes.

    +
    +

    5.1 Packages from CRAN

    +
    +
    ##########################################
    +# List of useful epidemiology R packages #
    +##########################################
    +
    +# This script uses the p_load() function from pacman R package, 
    +# which installs if package is absent, and loads for use if already installed
    +
    +
    +# Ensures the package "pacman" is installed
    +if (!require("pacman")) install.packages("pacman")
    +
    +
    +# Packages available from CRAN
    +##############################
    +pacman::p_load(
    +     
    +     # learning R
    +     ############
    +     learnr,   # interactive tutorials in RStudio Tutorial pane
    +     swirl,    # interactive tutorials in R console
    +        
    +     # project and file management
    +     #############################
    +     here,     # file paths relative to R project root folder
    +     rio,      # import/export of many types of data
    +     openxlsx, # import/export of multi-sheet Excel workbooks 
    +     
    +     # package install and management
    +     ################################
    +     pacman,   # package install/load
    +     renv,     # managing versions of packages when working in collaborative groups
    +     remotes,  # install from github
    +     
    +     # General data management
    +     #########################
    +     tidyverse,    # includes many packages for tidy data wrangling and presentation
    +          #dplyr,      # data management
    +          #tidyr,      # data management
    +          #ggplot2,    # data visualization
    +          #stringr,    # work with strings and characters
    +          #forcats,    # work with factors 
    +          #lubridate,  # work with dates
    +          #purrr       # iteration and working with lists
    +     linelist,     # cleaning linelists
    +     naniar,       # assessing missing data
    +     
    +     # statistics  
    +     ############
    +     janitor,      # tables and data cleaning
    +     gtsummary,    # making descriptive and statistical tables
    +     rstatix,      # quickly run statistical tests and summaries
    +     broom,        # tidy up results from regressions
    +     lmtest,       # likelihood-ratio tests
    +     easystats,
    +          # parameters, # alternative to tidy up results from regressions
    +          # see,        # alternative to visualise forest plots 
    +     
    +     # epidemic modeling
    +     ###################
    +     epicontacts,  # Analysing transmission networks
    +     EpiNow2,      # Rt estimation
    +     EpiEstim,     # Rt estimation
    +     projections,  # Incidence projections
    +     incidence2,   # Make epicurves and handle incidence data
    +     i2extras,     # Extra functions for the incidence2 package
    +     epitrix,      # Useful epi functions
    +     distcrete,    # Discrete delay distributions
    +     
    +     
    +     # plots - general
    +     #################
    +     #ggplot2,         # included in tidyverse
    +     patchwork,        # combining plots
    +     RColorBrewer,     # color scales
    +     ggnewscale,       # to add additional layers of color schemes
    +
    +     
    +     # plots - specific types
    +     ########################
    +     DiagrammeR,       # diagrams using DOT language
    +     incidence2,       # epidemic curves
    +     gghighlight,      # highlight a subset
    +     ggrepel,          # smart labels
    +     plotly,           # interactive graphics
    +     gganimate,        # animated graphics 
    +
    +     
    +     # gis
    +     ######
    +     sf,               # to manage spatial data using a Simple Feature format
    +     tmap,             # to produce simple maps, works for both interactive and static maps
    +     OpenStreetMap,    # to add OSM basemap in ggplot map
    +     spdep,            # spatial statistics 
    +     
    +     # routine reports
    +     #################
    +     rmarkdown,        # produce PDFs, Word Documents, Powerpoints, and HTML files
    +     reportfactory,    # auto-organization of R Markdown outputs
    +     officer,          # powerpoints
    +     
    +     # dashboards
    +     ############
    +     flexdashboard,    # convert an R Markdown script into a dashboard
    +     shiny,            # interactive web apps
    +     
    +     # tables for presentation
    +     #########################
    +     knitr,            # R Markdown report generation and html tables
    +     flextable,        # HTML tables
    +     #DT,              # HTML tables (alternative)
    +     #gt,              # HTML tables (alternative)
    +     #huxtable,        # HTML tables (alternative) 
    +     
    +     # phylogenetics
    +     ###############
    +     ggtree,           # visualization and annotation of trees
    +     ape,              # analysis of phylogenetics and evolution
    +     treeio            # to visualize phylogenetic files
    + 
    +)
    +
    +
    +
    +

    5.2 Packages from Github

    +

    Below are commmands to install two packages directly from Github repositories.

    +
      +
    • The development version of epicontacts contains the ability to make transmission trees with an temporal x-axis
      +
    • +
    • The epirhandbook package contains all the example data for this handbook and can be used to download the offline version of the handbook.
    • +
    +
    +
    # Packages to download from Github (not available on CRAN)
    +##########################################################
    +
    +# Development version of epicontacts (for transmission chains with a time x-axis)
    +pacman::p_install_gh("reconhub/epicontacts@timeline")
    +
    +# The package for this handbook, which includes all the example data  
    +pacman::p_install_gh("appliedepi/epirhandbook")
    +
    + + +
    + +
    + + +
    + + + + + + \ No newline at end of file diff --git a/new_pages/packages_suggested.qmd b/new_pages/packages_suggested.qmd index a902d77a..40864423 100644 --- a/new_pages/packages_suggested.qmd +++ b/new_pages/packages_suggested.qmd @@ -89,8 +89,7 @@ pacman::p_load( # plots - general ################# #ggplot2, # included in tidyverse - cowplot, # combining plots - # patchwork, # combining plots (alternative) + patchwork, # combining plots RColorBrewer, # color scales ggnewscale, # to add additional layers of color schemes @@ -103,6 +102,7 @@ pacman::p_load( ggrepel, # smart labels plotly, # interactive graphics gganimate, # animated graphics + ggalluvial, # for alluvial/sankey diagrams # gis @@ -110,6 +110,8 @@ pacman::p_load( sf, # to manage spatial data using a Simple Feature format tmap, # to produce simple maps, works for both interactive and static maps OpenStreetMap, # to add OSM basemap in ggplot map + tidyterra, # to plot basemaps + maptiles, # for creating basemaps spdep, # spatial statistics # routine reports @@ -146,7 +148,6 @@ pacman::p_load( Below are commmands to install two packages directly from Github repositories. -* The development version of **epicontacts** contains the ability to make transmission trees with an temporal x-axis * The **epirhandbook** package contains all the example data for this handbook and can be used to download the offline version of the handbook. @@ -154,9 +155,6 @@ Below are commmands to install two packages directly from Github repositories. # Packages to download from Github (not available on CRAN) ########################################################## -# Development version of epicontacts (for transmission chains with a time x-axis) -pacman::p_install_gh("reconhub/epicontacts@timeline") - # The package for this handbook, which includes all the example data pacman::p_install_gh("appliedepi/epirhandbook") diff --git a/new_pages/phylogenetic_trees.qmd b/new_pages/phylogenetic_trees.qmd index 76dd83da..2f1159ac 100644 --- a/new_pages/phylogenetic_trees.qmd +++ b/new_pages/phylogenetic_trees.qmd @@ -23,17 +23,17 @@ sample_data <- read.csv(here::here("data","phylo", "sample_data_Shigella_tree.cs ggtree(tree, layout="circular", branch.length='none') %<+% sample_data + # the %<+% is used to add your dataframe with sample data to the tree - aes(color=Belgium)+ # color the branches according to a variable in your dataframe + aes(color=Belgium) + # color the branches according to a variable in your dataframe scale_color_manual(name = "Sample Origin", # name of your color scheme (will show up in the legend like this) breaks = c("Yes", "No"), # the different options in your variable labels = c("NRCSS Belgium", "Other"), # how you want the different options named in your legend, allows for formatting values= c("blue", "black"), # the color you want to assign to the variable na.value = "black") + # color NA values in black as well - new_scale_color()+ # allows to add an additional color scheme for another variable - geom_tippoint(aes(color=Continent), size=1.5)+ # color the tip point by continent, you may change shape adding "shape = " + new_scale_color() + # allows to add an additional color scheme for another variable + geom_tippoint(aes(color=Continent), size=1.5) + # color the tip point by continent, you may change shape adding "shape = " scale_color_brewer(name = "Continent", # name of your color scheme (will show up in the legend like this) palette="Set1", # we choose a set of colors coming with the brewer package - na.value="grey")+ # for the NA values we choose the color grey + na.value="grey") + # for the NA values we choose the color grey theme(legend.position= "bottom") ``` @@ -44,7 +44,27 @@ scale_color_brewer(name = "Continent", # name of your color scheme (will show u ### Load packages {.unnumbered} -This code chunk shows the loading of required packages. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. +This code chunk shows the loading of required packages. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. + +However, the packages **ggtree* and **treeio** are not found on [CRAN](https://cran.r-project.org/), and are found on an alternative package repository called [Bioconductor](https://www.bioconductor.org/install/), which is a collection of open source bioinformatics packages and software. To install these first, we need to use the package **BiocManager**. + +```{r, warning=F, message=F} + +#Load and install BiocManager +pacman::p_load( + BiocManager +) + +#Install ggtree and treeio - note these are in quotation marks " " +BiocManager::install(c( + "ggtree", + "treeio" +)) + + +``` + +Now that those packages are installed, we can load them with `pacman::p_load()`. ```{r, phylogenetic_trees_loading_packages} pacman::p_load( @@ -54,6 +74,7 @@ pacman::p_load( ape, # to import and export phylogenetic files ggtree, # to visualize phylogenetic files treeio, # to visualize phylogenetic files + tidytree, # to visualize phylogenetic files ggnewscale) # to add additional layers of color schemes ``` @@ -62,7 +83,7 @@ pacman::p_load( The data for this page can be downloaded with the instructions on the [Download handbook and data](data_used.qmd) page. -There are several different formats in which a phylogenetic tree can be stored (eg. Newick, NEXUS, Phylip). A common one is the Newick file format (.nwk), which is the standard for representing trees in computer-readable form. This means an entire tree can be expressed in a string format such as "((t2:0.04,t1:0.34):0.89,(t5:0.37,(t4:0.03,t3:0.67):0.9):0.59); ", listing all nodes and tips and their relationship (branch length) to each other. +There are several different formats in which a phylogenetic tree can be stored (eg. Newick, NEXUS, Phylip). A common one is the Newick file format (.nwk), which is the standard for representing trees in computer-readable form. This means an entire tree can be expressed in a string format such as `"((t2:0.04,t1:0.34):0.89,(t5:0.37, (t4:0.03,t3:0.67):0.9):0.59);"`, listing all nodes and tips and their relationship (branch length) to each other. Note: It is important to understand that the phylogenetic tree file in itself does not contain sequencing data, but is merely the result of the genetic distances between the sequences. We therefore cannot extract sequencing data from a tree file. @@ -87,7 +108,7 @@ tree Second, we import a table stored as a .csv file with additional information for each sequenced sample, such as gender, country of origin and attributes for antimicrobial resistance, using the `import()` function from the **rio** package: -```{r, echo=F} +```{r, echo=F, warning=F, message=F} sample_data <- import(here("data", "phylo", "sample_data_Shigella_tree.csv")) ``` @@ -172,17 +193,17 @@ Here is an example of a circular tree: ```{r, phylogenetic_trees_adding_sampledata, fig.align='center', warning=F, message=F} ggtree(tree, layout = "circular", branch.length = 'none') %<+% sample_data + # %<+% adds dataframe with sample data to tree - aes(color = Belgium)+ # color the branches according to a variable in your dataframe + aes(color = Belgium) + # color the branches according to a variable in your dataframe scale_color_manual( name = "Sample Origin", # name of your color scheme (will show up in the legend like this) breaks = c("Yes", "No"), # the different options in your variable labels = c("NRCSS Belgium", "Other"), # how you want the different options named in your legend, allows for formatting values = c("blue", "black"), # the color you want to assign to the variable na.value = "black") + # color NA values in black as well - new_scale_color()+ # allows to add an additional color scheme for another variable + new_scale_color() + # allows to add an additional color scheme for another variable geom_tippoint( mapping = aes(color = Continent), # tip color by continent. You may change shape adding "shape = " - size = 1.5)+ # define the size of the point at the tip + size = 1.5) + # define the size of the point at the tip scale_color_brewer( name = "Continent", # name of your color scheme (will show up in the legend like this) palette = "Set1", # we choose a set of colors coming with the brewer package @@ -192,8 +213,8 @@ ggtree(tree, layout = "circular", branch.length = 'none') %<+% sample_data + # % offset = 1, size = 1, geom = "text", - align = TRUE)+ - ggtitle("Phylogenetic tree of Shigella sonnei")+ # title of your graph + align = TRUE) + + ggtitle("Phylogenetic tree of Shigella sonnei") + # title of your graph theme( axis.title.x = element_blank(), # removes x-axis title axis.title.y = element_blank(), # removes y-axis title @@ -285,7 +306,7 @@ ggtree( tree, branch.length = 'none', layout = 'circular') %<+% sample_data + # we add the asmple data using the %<+% operator - geom_tiplab(size = 1)+ # label tips of all branches with sample name in tree file + geom_tiplab(size = 1) + # label tips of all branches with sample name in tree file geom_text2( mapping = aes(subset = !isTip, label = node), size = 3, @@ -326,7 +347,7 @@ Lets have a look at the subset tree 2: ```{r} ggtree(sub_tree2) + - geom_tiplab(size =3) + + geom_tiplab(size = 3) + ggtitle("Subset tree 2") ``` @@ -410,14 +431,14 @@ ggtree(sub_tree2) %<+% sample_data + # we use th %<+% operator to link to th size = 2.5, offset = 0.001, align = TRUE) + - theme_tree2()+ - xlim(0, 0.015)+ # set the x-axis limits of our tree + theme_tree2() + + xlim(0, 0.015) + # set the x-axis limits of our tree geom_tippoint(aes(color=Country), # color the tip point by continent - size = 1.5)+ + size = 1.5) + scale_color_brewer( name = "Country", palette = "Set1", - na.value = "grey")+ + na.value = "grey") + geom_tiplab( # add isolation year as a text label at the tips aes(label = Year), color = 'blue', @@ -425,7 +446,7 @@ ggtree(sub_tree2) %<+% sample_data + # we use th %<+% operator to link to th size = 3, linetype = "blank" , geom = "text", - align = TRUE)+ + align = TRUE) + geom_tiplab( # add travel history as a text label at the tips, in red color aes(label = Travel_history), color = 'red', @@ -433,9 +454,9 @@ ggtree(sub_tree2) %<+% sample_data + # we use th %<+% operator to link to th size = 3, linetype = "blank", geom = "text", - align = TRUE)+ - ggtitle("Phylogenetic tree of Belgian S. sonnei strains with travel history")+ # add plot title - xlab("genetic distance (0.001 = 4 nucleotides difference)")+ # add a label to the x-axis + align = TRUE) + + ggtitle("Phylogenetic tree of Belgian S. sonnei strains with travel history") + # add plot title + xlab("genetic distance (0.001 = 4 nucleotides difference)") + # add a label to the x-axis theme( axis.title.x = element_text(size = 10), axis.title.y = element_blank(), @@ -457,8 +478,8 @@ We can add more complex information, such as categorical presence of antimicrobi First we need to plot our tree (this can be either linear or circular) and store it in a new ggtree plot object `p`: We will use the sub_tree from part 3.) ```{r, phylogenetic_trees_sampledata_heatmap, out.width=c('60%'), fig.align='center', fig.show='hold'} -p <- ggtree(sub_tree2, branch.length='none', layout='circular') %<+% sample_data + - geom_tiplab(size =3) + +p <- ggtree(sub_tree2, branch.length = 'none', layout = 'circular') %<+% sample_data + + geom_tiplab(size = 3) + theme( legend.position = "none", axis.title.x = element_blank(), @@ -538,7 +559,7 @@ h3 <- gheatmap(h2, cipR, # adds the second row of heatmap describing Cip theme(legend.position = "bottom", legend.title = element_text(size = 12), legend.text = element_text(size = 10), - legend.box = "vertical", legend.margin = margin())+ + legend.box = "vertical", legend.margin = margin()) + guides(fill = guide_legend(nrow = 2,byrow = TRUE)) h3 ``` @@ -552,12 +573,12 @@ h4 <- h3 + new_scale_fill() h5 <- gheatmap(h4, MIC_Cip, offset = 14, width = 0.10, - colnames = FALSE)+ + colnames = FALSE) + scale_fill_continuous(name = "MIC for Ciprofloxacin", # here we define a gradient color scheme for the continuous variable of MIC low = "yellow", high = "red", breaks = c(0, 0.50, 1.00), na.value = "white") + - guides(fill = guide_colourbar(barwidth = 5, barheight = 1))+ + guides(fill = guide_colourbar(barwidth = 5, barheight = 1)) + theme(legend.position = "bottom", legend.title = element_text(size = 12), legend.text = element_text(size = 10), @@ -571,9 +592,9 @@ We can do the same exercise for a linear tree: p <- ggtree(sub_tree2) %<+% sample_data + geom_tiplab(size = 3) + # labels the tips - theme_tree2()+ - xlab("genetic distance (0.001 = 4 nucleotides difference)")+ - xlim(0, 0.015)+ + theme_tree2() + + xlab("genetic distance (0.001 = 4 nucleotides difference)") + + xlim(0, 0.015) + theme(legend.position = "none", axis.title.y = element_blank(), plot.title = element_text(size = 12, @@ -591,11 +612,11 @@ h1 <- gheatmap(p, gender, offset = 0.003, width = 0.1, color="black", - colnames = FALSE)+ + colnames = FALSE) + scale_fill_manual(name = "Gender", values = c("#00d1b1", "purple"), breaks = c("Male", "Female"), - labels = c("Male", "Female"))+ + labels = c("Male", "Female")) + theme(legend.position = "bottom", legend.title = element_text(size = 12), legend.text = element_text(size = 10), @@ -614,15 +635,15 @@ h3 <- gheatmap(h2, cipR, offset = 0.004, width = 0.1, color = "black", - colnames = FALSE)+ + colnames = FALSE) + scale_fill_manual(name = "Ciprofloxacin resistance \n conferring mutation", values = c("#fe9698","#ea0c92"), breaks = c( "gyrA D87Y", "gyrA S83L"), - labels = c( "gyrA d87y", "gyrA s83l"))+ + labels = c( "gyrA d87y", "gyrA s83l")) + theme(legend.position = "bottom", legend.title = element_text(size = 12), legend.text = element_text(size = 10), - legend.box = "vertical", legend.margin = margin())+ + legend.box = "vertical", legend.margin = margin()) + guides(fill = guide_legend(nrow = 2,byrow = TRUE)) h3 ``` @@ -636,16 +657,16 @@ h5 <- gheatmap(h4, MIC_Cip, offset = 0.005, width = 0.1, color = "black", - colnames = FALSE)+ + colnames = FALSE) + scale_fill_continuous(name = "MIC for Ciprofloxacin", low = "yellow", high = "red", breaks = c(0,0.50,1.00), - na.value = "white")+ - guides(fill = guide_colourbar(barwidth = 5, barheight = 1))+ + na.value = "white") + + guides(fill = guide_colourbar(barwidth = 5, barheight = 1)) + theme(legend.position = "bottom", legend.title = element_text(size = 10), legend.text = element_text(size = 8), - legend.box = "horizontal", legend.margin = margin())+ + legend.box = "horizontal", legend.margin = margin()) + guides(shape = guide_legend(override.aes = list(size = 2))) h5 @@ -655,11 +676,9 @@ h5 ## Resources {} -http://hydrodictyon.eeb.uconn.edu/eebedia/index.php/Ggtree# Clade_Colors -https://bioconductor.riken.jp/packages/3.2/bioc/vignettes/ggtree/inst/doc/treeManipulation.html -https://guangchuangyu.github.io/ggtree-book/chapter-ggtree.html -https://bioconductor.riken.jp/packages/3.8/bioc/vignettes/ggtree/inst/doc/treeManipulation.html - -Ea Zankari, Rosa Allesøe, Katrine G Joensen, Lina M Cavaco, Ole Lund, Frank M Aarestrup, PointFinder: a novel web tool for WGS-based detection of antimicrobial resistance associated with chromosomal point mutations in bacterial pathogens, Journal of Antimicrobial Chemotherapy, Volume 72, Issue 10, October 2017, Pages 2764–2768, https://doi.org/10.1093/jac/dkx217 +[ggtree](http://hydrodictyon.eeb.uconn.edu/eebedia/index.php/Ggtree) +[Tree manipulation in ggtree](https://bioconductor.riken.jp/packages/3.2/bioc/vignettes/ggtree/inst/doc/treeManipulation.html) +[Visualisation and annotation of phylogenetic trees in ggtree](https://guangchuangyu.github.io/ggtree-book/chapter-ggtree.html) +[PointFinder: a novel web tool for WGS-based detection of antimicrobial resistance associated with chromosomal point mutations in bacterial pathogens](https://academic.oup.com/jac/article/72/10/2764/3979530) diff --git a/new_pages/pivoting.qmd b/new_pages/pivoting.qmd index a3e8b62e..69de3a6b 100644 --- a/new_pages/pivoting.qmd +++ b/new_pages/pivoting.qmd @@ -16,8 +16,8 @@ knitr::include_graphics(here::here("images", "pivoting", "Pivoting_500x500.png") When managing data, *pivoting* can be understood to refer to one of two processes: -1. The creation of *pivot tables*, which are tables of statistics that summarise the data of a more extensive table -2. The conversion of a table from **long** to **wide** format, or vice versa. +1. The creation of *pivot tables*, which are tables of statistics that summarise the data of a more extensive table. +2. The conversion of a table from **long** to **wide** format, or from **wide** to **long**. **In this page, we will focus on the latter definition.** The former is a crucial step in data analysis, and is covered elsewhere in the [Grouping data](grouping.qmd) and [Descriptive tables](tables_descriptive.qmd) pages. @@ -50,7 +50,7 @@ pacman::p_load( In this page, we will use a fictional dataset of daily malaria cases, by facility and age group. If you want to follow along, click here to download (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} count_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% as_tibble() ``` @@ -80,7 +80,7 @@ linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.r ```{r, eval=F} # import your dataset -linelist <- import("linelist_cleaned.xlsx") +linelist <- import("linelist_cleaned.rds") ``` @@ -108,7 +108,7 @@ Let us take the `count_data` dataset imported in the Preparation section above a DT::datatable(count_data, rownames = FALSE, options = list(pageLength = 5, scrollX=T) ) ``` -Each observation in this dataset refers to the malaria counts at one of 65 facilities on a given date, ranging from ` count_data$data_date %>% min()` to ` count_data$data_date %>% max()`. These facilities are located in one `Province` (North) and four `District`s (Spring, Bolo, Dingo, and Barnard). The dataset provides the overall counts of malaria, as well as age-specific counts in each of three age groups - <4 years, 5-14 years, and 15 years and older. +Each observation in this dataset refers to the malaria counts at one of 65 facilities on a given date, ranging from ` count_data$data_date %>% min()` to ` count_data$data_date %>% max()`. These facilities are located in one `Province` (North) and four `District` (Spring, Bolo, Dingo, and Barnard). The dataset provides the overall counts of malaria, as well as age-specific counts in each of three age groups - <4 years, 5-14 years, and 15 years and older. "Wide" data like this are not adhering to "tidy data" standards, because the column headers do not actually represent "variables" - they represent *values* of a hypothetical "age group" variable. @@ -131,13 +131,13 @@ However, what if we wanted to display the relative contributions of each age gro ### `pivot_longer()` {.unnumbered} -The **tidyr** function `pivot_longer()` makes data "longer". **tidyr** is part of the **tidyverse** of R packages. +The **tidyr** function `pivot_longer()` makes data "longer". **tidyr** is part of the [**tidyverse**](https://www.tidyverse.org/) of R packages. It accepts a range of columns to transform (specified to `cols = `). Therefore, it can operate on only a part of a dataset. This is useful for the malaria data, as we only want to pivot the case count columns. In this process, you will end up with two "new" columns - one with the categories (the former column names), and one with the corresponding values (e.g. case counts). You can accept the default names for these new columns, or you can specify your own to `names_to = ` and `values_to = ` respectively. -Let's see `pivot_longer()` in action... +Let's see `pivot_longer()` in action. @@ -154,7 +154,7 @@ df_long <- count_data %>% df_long ``` -Notice that the newly created data frame (`df_long`) has more rows (12,152 vs 3,038); it has become *longer*. In fact, it is precisely four times as long, because each row in the original dataset now represents four rows in df_long, one for each of the malaria count observations (<4y, 5-14y, 15y+, and total). +Notice that the newly created data frame (`df_long`) has more rows (12,152 vs 3,038); it has become *longer*. In fact, it is precisely four times as long, because each row in the original dataset now represents four rows in `df_long`, one for each of the malaria count observations (<4y, 5-14y, 15y+, and total). In addition to becoming longer, the new dataset has fewer columns (8 vs 10), as the data previously stored in four columns (those beginning with the prefix `malaria_`) is now stored in two. @@ -253,10 +253,12 @@ However, there will be many cases when, as a field epidemiologist, you will be w One particularly common problem you will encounter will be the need to pivot columns that contain different classes of data. This pivot will result in storing these different data types in a single column, which is not a good situation. There are various approaches one can take to separate out the mess this creates, but there is an important step you can take using `pivot_longer()` to avoid creating such a situation yourself. -Take a situation in which there have been a series of observations at different time steps for each of three items A, B and C. Examples of such items could be individuals (e.g. contacts of an Ebola case being traced each day for 21 days) or remote village health posts being monitored once per year to ensure they are still functional. Let's use the contact tracing example. Imagine that the data are stored as follows: +Take a situation in which there have been a series of observations at different time steps for each of three items A, B and C. Examples of such items could be individuals (e.g. contacts of an Ebola case being traced each day for 21 days) or remote village health posts being monitored once per year to ensure they are still functional. +Let's use the contact tracing example. You can download the data [here](https://github.com/appliedepi/epiRhandbook_eng/blob/master/data/contact_tracing.rds). -```{r, message=FALSE, echo=F} + +```{r, message = FALSE, echo = T} df <- tibble::tribble( @@ -291,7 +293,7 @@ To prevent this situation, we can take advantage of the syntax structure of the We do this by: -* Providing a character vector to the `names_to = ` argument, with the second item being (`".value"` ). This special term indicates that the pivoted columns will be split based on a character in their name... +* Providing a character vector to the `names_to = ` argument, with the second item being (`".value"` ). This special term indicates that the pivoted columns will be split based on a character in their name. * You must also provide the "splitting" character to the `names_sep = ` argument. In this case, it is the underscore "_". Thus, the naming and split of new columns is based around the underscore in the existing variable names. @@ -314,7 +316,7 @@ __Finishing touches__: Note that the `date` column is currently in *character* class - we can easily convert this into it's proper date class using the `mutate()` and `as_date()` functions described in the [Working with dates](dates.qmd) page. -We may also want to convert the `observation` column to a `numeric` format by dropping the "obs" prefix and converting to numeric. We cando this with `str_remove_all()` from the **stringr** package (see the [Characters and strings](characters_strings.qmd) page). +We may also want to convert the `observation` column to a `numeric` format by dropping the "obs" prefix and converting to numeric. We can do this with `str_remove_all()` from the **stringr** package (see the [Characters and strings](characters_strings.qmd) page). ```{r} @@ -360,11 +362,11 @@ knitr::include_graphics(here::here("images", "pivoting", "pivot_wider_new.png")) In some instances, we may wish to convert a dataset to a wider format. For this, we can use the `pivot_wider()` function. -A typical use-case is when we want to transform the results of an analysis into a format which is more digestible for the reader (such as a [Table for presentation][Tables for presentation]). Usually, this involves transforming a dataset in which information for one subject is are spread over multiple rows into a format in which that information is stored in a single row. +A typical use-case is when we want to transform the results of an analysis into a format which is more digestible for the reader (such as a [Tables for presentation](tables_presentation.qmd)). Usually, this involves transforming a dataset in which information for one subject is are spread over multiple rows into a format in which that information is stored in a single row. ### Data {.unnumbered} -For this section of the page, we will use the case linelist (see the [Preparation](#pivot_prep) section), which contains one row per case. +For this section of the page, we will use the `linelist` dataset (see the [Preparation](#pivot_prep) section), which contains one row per case. Here are the first 50 rows: @@ -409,7 +411,7 @@ table_wide <- table_wide ``` -This table is much more reader-friendly, and therefore better for inclusion in our reports. You can convert into a pretty table with several packages including **flextable** and **knitr**. This process is elaborated in the page [Tables for presentation](tables_descriptive.qmd). +This table is much more reader-friendly, and therefore better for inclusion in our reports. You can convert into a pretty table with several packages including **flextable** and **knitr**. This process is elaborated in the page [Tables for presentation](tables_presentation.qmd). ```{r} table_wide %>% diff --git a/new_pages/quarto.qmd b/new_pages/quarto.qmd new file mode 100644 index 00000000..427608aa --- /dev/null +++ b/new_pages/quarto.qmd @@ -0,0 +1,142 @@ + +# Quarto { } + +```{r out.width = "100%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/0_quartoimg.PNG")) +``` + +```{r, echo = F, message=F, warning = F} + +library(tidyverse) +library(rio) +library(here) + +linelist <- import(here("data", "case_linelists", "linelist_cleaned.rds")) + +``` + +Quarto is an alternative approach to R Markdown for creating automated, reproducible reports, presentations and interactive dashboards. Quarto was developed by the team behind RStudio, to take the functionality of R Markdown, with other approaches, and combine them into a single consistent system. + +As the overall philosophy and syntax of Quarto is nearly identical to that of R Markdown, this chapter will not focus on these, and instead refer you to the [R Markdown chapter](rmarkdown.qmd) for instructions on syntax and application. We will approach the following topics: + +1) Why Quarto over R Markdown? +2) How to get started in Quarto. +3) Going beyond reports to dashboards and interactive documents. + +## Why Quarto over R Markdown? + +Quarto provides the flexibility and automation that R Markdown pioneered, with a more powerful underlying flexibility that streamlines a lot of the sharing and publishing processes of your work! While R Markdown is tied to R, Quarto documents allow you to use a number of different programming languages such as R, Python, Javascript and Julia. While R Markdown [can do this](https://bookdown.org/yihui/rmarkdown/language-engines.html), the approach is less straightforward. By streamlining the inclusion of multiple programming languages, it makes collaborating across different people and groups, easier if multiple approaches are used. + +Even if you are only working in R, Quarto has several advantages. Rather than relying on individual packages to create different outputs, Quarto handles this all internally. To produce a website, PowerPoint slides, or a html report, rather than using external packages, Quarto has this functionality inbuilt. This means you have fewer packages to update, and if one of the packages is no longer maintained, you don't need to change your approach to maintain the same output. + +Quarto also has the advantage of providing a way to render multiple files at the same time, and combine them when wanted. The Epi R Handbook is written as several individual Quarto files, and then combined to make this website! + +If you are thinking of moving over from R Markdown to Quarto, don't worry, you won't have to re-write every R Markdown document to move over to Quarto! Quarto documents follow the same syntax and commands as Rmarkdown. Previous scripts in R Markdown can generally be moved over to Quarto without changing any of your code! + +Below is the pipeline through which Quarto documents are created, as you can see it is very similar to the [R Markdown pipeline]([R Markdown document in Rstudio](rmarkdown.qmd)), other than the initial file type as changed. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/rstudio-qmd-how-it-works.png")) +``` +[Source](https://quarto.org/docs/faq/rmarkdown.html) + +### Limitations of Quarto {.unnumbered} + +There is one substantial limitation of Quarto, compared to R Markdown, when it is used to generate multiple different reports using the same dataset. + +In R Markdown, you can load in a dataset, run your analyses, and then generate multiple different reports from this dataset in your R environment. This means that you *do not need to individually import the dataset for each report*. + +However, because of the way that Quarto works, every single report needs to be self contained. That is to say that you would need to import the dataset *every time you want a new report*. + +This is not a problem if you are only running a single report, or a handful. But if you have a large dataset, and need to make a large number of reports, this can quickly become a real problem. + +Imagine a scenario where you are conducting a national outbreak response. You have a dataset which takes 5 minutes to import, and then you need to generate a report for each of the 50 provinces in the country. + +* For R Markdown, you would only need to read in the data once, and then run your reports. This would take a minimum of 5 minutes (5 minutes to read in the data + time to run the script for the reports). + +* For Quarto, you would need to read in the dataset each time. This would mean you would need at least 250 minutes (5 minutes for each of the 50 provinces). + +This quickly increasing time burden means that if you are generating multiple reports based on a large dataset, you may want to use R Markdown over Quarto! + +For further explanation, please see these forum posts: + +[Quarto can't find R-objects](https://forum.posit.co/t/quarto-cant-find-r-objects/156848) + +[How to quarto_render into console environment](https://github.com/quarto-dev/quarto-cli/discussions/1773) + +## Getting started + +Creating a Quarto document does not require us to download an additional package, unlike R Markdown which requires the **rmarkdown** package. Additionally, you do not need to install LaTex, as you do with R Markdown, as Quarto contains built in functionality. + +Functionally, Quarto works the same way as R Markdown. You create your Quarto script (instead of your R Markdown file), write your code, and knit the document. + +First, just like when you create an [R Markdown document in Rstudio](rmarkdown.qmd) you start with `File`, then `New file`, then `R Markdown`. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/1_createquarto.PNG")) +``` + +You will then get a number of different options to choose. Here we will select "HTML" to create an html document. All these details can be changed later in the document, so do not worry if you change your mind later. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/2_namingquarto.PNG")) +``` + +This will create your new Quarto script. *Note: While the R Markdown scripts ended with .Rmd, Quarto scripts end with .qmd* + +While R Markdown scripts set the working directory to wherever the file is located, Quarto documents retain the original working directory. This is especially useful when you are working with an [R Project](r_projects.qmd). + +Like for R Markdown, Quarto used in RStudio allows you to see what the rendered document will look like after it has been knit. To switch between the "Visual" and "Source" mode, click the "Visual" button in the top left side of the script. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/3_quartovisual.PNG")) +``` + +You are now ready to code your Quarto script! The syntax and approach is the same as creating an R Markdown document, so see the chapter on [Reports with R Markdown](rmarkdown.qmd) for guidance and inspiration. + +Here is an example of what a Quarto script for analysing our `linelist` data set might look like. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/4_quarto_script.png")) +``` + +And here is what the output looks like. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "quarto/5_quarto_report.png")) +``` + + + + + + +## Moving beyond simple reports + +You may want to move beyond creating simple, static, reports to interactive dashboards and outputs, like you can in [R Markdown](flexdashboard.qmd). Luckily you can do all of this in Quarto, using inbuilt functionality, and other packages like [Shiny](shiny_basics.qmd)! For an example of how far you can take your Quarto scripts, see this [Quarto Gallery](https://quarto.org/docs/gallery/). + + + + + + +## Resources + +[I'm an R user: Quarto or R Markdown?](https://www.jumpingrivers.com/blog/quarto-rmarkdown-comparison/) + +[FAQ for R Markdown Users](https://quarto.org/docs/faq/rmarkdown.html#quarto-sounds-similar-to-r-markdown.-what-is-the-difference-and-why-create-a-new-project) + +[Quarto tutorial](https://quarto.org/docs/get-started/hello/rstudio.html) + +[Quarto Gallery](https://quarto.org/docs/gallery/) + +[Create & Publish a Quarto Blog on QUarto Pub in 100 seconds](https://www.youtube.com/watch?v=t8qtcDyCRFA) + + + + + + + + + diff --git a/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.css b/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.css new file mode 100644 index 00000000..285e4448 --- /dev/null +++ b/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2078 @@ +/*! + * 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?2820a3852bdb9a5832199cc61cec4e65") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.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-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.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/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.woff b/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 00000000..dbeeb055 Binary files /dev/null and b/new_pages/quarto_files/libs/bootstrap/bootstrap-icons.woff differ diff --git a/new_pages/quarto_files/libs/bootstrap/bootstrap.min.css b/new_pages/quarto_files/libs/bootstrap/bootstrap.min.css new file mode 100644 index 00000000..3beee0f3 --- /dev/null +++ b/new_pages/quarto_files/libs/bootstrap/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * 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,[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: 17px;--bs-body-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";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--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: #0d6efd;--bs-link-color-rgb: 13, 110, 253;--bs-link-decoration: underline;--bs-link-hover-color: #0a58ca;--bs-link-hover-color-rgb: 10, 88, 202;--bs-code-color: #7d12ba;--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.375rem;--bs-border-radius-sm: 0.25rem;--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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6);border-radius:.375rem}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:#f8f9fa;border-radius:.375rem;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:.25rem}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:.375rem;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:.375rem;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:.25rem}.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:.375rem}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:.375rem}.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:.375rem;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:.25rem}.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:.25em;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:.375rem}.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:.375rem}.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:.25rem}.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:.375rem}.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:.375rem}.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.375rem;--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: #0d6efd;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #0a58ca;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #0a58ca;--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: 49, 132, 253;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.25rem}.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.375rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.375rem - 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:.375rem}.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: #0d6efd;--bs-nav-link-hover-color: #0a58ca;--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.375rem;--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.375rem;--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(253, 254, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fdfeff;--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: #fdfeff;--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.375rem;--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(253, 254, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fdfeff;--bs-navbar-brand-color: #fdfefe;--bs-navbar-brand-hover-color: #fdfeff;--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.375rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.375rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(33, 37, 41, 0.03);--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.375rem;--bs-accordion-inner-border-radius: calc(0.375rem - 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: #0d6efd;--bs-pagination-bg: #ffffff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.375rem;--bs-pagination-hover-color: #0a58ca;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #0a58ca;--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.25rem}.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.375rem;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.375rem;--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.375rem;--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.375rem;--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:.375rem;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.375rem;--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.375rem;--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: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";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;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: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";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;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.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: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}.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-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}.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)}.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)}}@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}}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)}}.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)}: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.375rem);--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}}: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%)}.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}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.375rem;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:rgba(33,37,41,.75)}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}dd code:not(.sourceCode),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}.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 +* +* 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: rgba(33, 37, 41, 0.75);--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.375rem}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] 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}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(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [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] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [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(800px - 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(800px - 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(50px, 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, 200px) [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(800px - 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(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.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(800px - 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(1250px - 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(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: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(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.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(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] 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(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.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] 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(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.375rem}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:#f8f9fa;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:rgba(33,37,41,.75);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:rgba(33,37,41,.75);margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#0d6efd}.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:#0d6efd}.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 #0d6efd;color:#0d6efd !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#0d6efd !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:.375rem;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:rgba(33,37,41,.75)}div.callout.callout-style-default>.callout-header{background-color:rgba(33,37,41,.75)}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:.375rem;border-bottom-right-radius:.375rem;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(233,236,239,.65)}.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:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;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}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}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:.375rem;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:repeat(2, 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/new_pages/quarto_files/libs/bootstrap/bootstrap.min.js b/new_pages/quarto_files/libs/bootstrap/bootstrap.min.js new file mode 100644 index 00000000..e8f21f70 --- /dev/null +++ b/new_pages/quarto_files/libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * 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=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/new_pages/quarto_files/libs/clipboard/clipboard.min.js b/new_pages/quarto_files/libs/clipboard/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/new_pages/quarto_files/libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * 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 b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/new_pages/quarto_files/libs/quarto-html/popper.min.js b/new_pages/quarto_files/libs/quarto-html/popper.min.js new file mode 100644 index 00000000..e3726d72 --- /dev/null +++ b/new_pages/quarto_files/libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.7 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/new_pages/quarto_files/libs/quarto-html/quarto-syntax-highlighting.css b/new_pages/quarto_files/libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 00000000..b30ce576 --- /dev/null +++ b/new_pages/quarto_files/libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,205 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; + font-style: inherit; +} + +code span.at { + color: #657422; + font-style: inherit; +} + +code span.ss { + color: #20794D; + font-style: inherit; +} + +code span.an { + color: #5E5E5E; + font-style: inherit; +} + +code span.fu { + color: #4758AB; + font-style: inherit; +} + +code span.st { + color: #20794D; + font-style: inherit; +} + +code span.cf { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +code span.op { + color: #5E5E5E; + font-style: inherit; +} + +code span.er { + color: #AD0000; + font-style: inherit; +} + +code span.bn { + color: #AD0000; + font-style: inherit; +} + +code span.al { + color: #AD0000; + font-style: inherit; +} + +code span.va { + color: #111111; + font-style: inherit; +} + +code span.bu { + font-style: inherit; +} + +code span.ex { + font-style: inherit; +} + +code span.pp { + color: #AD0000; + font-style: inherit; +} + +code span.in { + color: #5E5E5E; + font-style: inherit; +} + +code span.vs { + color: #20794D; + font-style: inherit; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; + font-style: inherit; +} + +code span.ch { + color: #20794D; + font-style: inherit; +} + +code span.dt { + color: #AD0000; + font-style: inherit; +} + +code span.fl { + color: #AD0000; + font-style: inherit; +} + +code span.co { + color: #5E5E5E; + font-style: inherit; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; + font-style: inherit; +} + +code span.sc { + color: #5E5E5E; + font-style: inherit; +} + +code span.dv { + color: #AD0000; + font-style: inherit; +} + +code span.kw { + color: #003B4F; + font-weight: bold; + font-style: inherit; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > *, .margin-caption, .aside" + ); + + let lastBottom = 0; + for (const marginChild of marginChildren) { + if (marginChild.offsetParent !== null) { + // clear the top margin so we recompute it + marginChild.style.marginTop = null; + const top = marginChild.getBoundingClientRect().top + window.scrollY; + if (top < lastBottom) { + const marginChildStyle = window.getComputedStyle(marginChild); + const marginBottom = parseFloat(marginChildStyle["marginBottom"]); + const margin = lastBottom - top + marginBottom; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Recompute the position of margin elements anytime the body size changes + if (window.ResizeObserver) { + const resizeObserver = new window.ResizeObserver( + throttle(() => { + layoutMarginEls(); + if ( + window.document.body.getBoundingClientRect().width < 990 && + isReaderMode() + ) { + quartoToggleReader(); + } + }, 50) + ); + resizeObserver.observe(window.document.body); + } + + const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); + const sidebarEl = window.document.getElementById("quarto-sidebar"); + const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); + const marginSidebarEl = window.document.getElementById( + "quarto-margin-sidebar" + ); + // function to determine whether the element has a previous sibling that is active + const prevSiblingIsActiveLink = (el) => { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id="${anchor}"]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + // This is the no-scroll case where last section should be the active one + sectionIndex = 0; + } else { + // This finds the last section visible on screen that should be made active + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + let elRect; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + child.style.pointerEvents = "none"; + } + + nexttick(() => { + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append( + titleEl.textContent || titleEl.innerText, + toggleIcon + ); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.pointerEvents = null; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + if (!elRect) { + elRect = el.getBoundingClientRect(); + } + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + positionToggle(); + + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + positionToggle(); + }, 50) + ); + + window.addEventListener("quarto-hrChanged", () => { + elRect = undefined; + }); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }); + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + child.style.pointerEvents = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); + for (const tabEl of tabEls) { + const id = tabEl.getAttribute("data-bs-target"); + if (id) { + const columnEl = document.querySelector( + `${id} .column-margin, .tabset-margin-content` + ); + if (columnEl) + tabEl.addEventListener("shown.bs.tab", function (event) { + const el = event.srcElement; + if (el) { + const visibleCls = `${el.id}-margin-content`; + // walk up until we find a parent tabset + let panelTabsetEl = el.parentElement; + while (panelTabsetEl) { + if (panelTabsetEl.classList.contains("panel-tabset")) { + break; + } + panelTabsetEl = panelTabsetEl.parentElement; + } + + if (panelTabsetEl) { + const prevSib = panelTabsetEl.previousElementSibling; + if ( + prevSib && + prevSib.classList.contains("tabset-margin-container") + ) { + const childNodes = prevSib.querySelectorAll( + ".tabset-margin-content" + ); + for (const childEl of childNodes) { + if (childEl.classList.contains(visibleCls)) { + childEl.classList.remove("collapse"); + } else { + childEl.classList.add("collapse"); + } + } + } + } + } + + layoutMarginEls(); + }); + } + } + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const boundRect = el.getBoundingClientRect(); + const top = + boundRect.top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + let hasObserved = false; + const visibleItemObserver = (els) => { + let visibleElements = [...els]; + const intersectionObserver = new IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (visibleElements.indexOf(entry.target) === -1) { + visibleElements.push(entry.target); + } + } else { + visibleElements = visibleElements.filter((visibleEntry) => { + return visibleEntry !== entry; + }); + } + }); + + if (!hasObserved) { + hideOverlappedSidebars(); + } + hasObserved = true; + }, + {} + ); + els.forEach((el) => { + intersectionObserver.observe(el); + }); + + return { + getVisibleEntries: () => { + return visibleElements; + }, + }; + }; + + const rightElementObserver = visibleItemObserver(rightSideConflictEls); + const leftElementObserver = visibleItemObserver(leftSideConflictEls); + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); + sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility( + toRegions(leftElementObserver.getVisibleEntries()) + ); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); + const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (tocOpenDepth === -1 && depth > 1) { + // toc-expand: false + el.classList.add("collapse"); + } else if ( + depth <= tocOpenDepth || + hasActiveChild || + prevSiblingIsActiveLink(el) + ) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/new_pages/quarto_files/libs/quarto-html/tippy.css b/new_pages/quarto_files/libs/quarto-html/tippy.css new file mode 100644 index 00000000..e6ae635c --- /dev/null +++ b/new_pages/quarto_files/libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/new_pages/quarto_files/libs/quarto-html/tippy.umd.min.js b/new_pages/quarto_files/libs/quarto-html/tippy.umd.min.js new file mode 100644 index 00000000..ca292be3 --- /dev/null +++ b/new_pages/quarto_files/libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/new_pages/r_projects.qmd b/new_pages/r_projects.qmd index 276d1b63..e445ce74 100644 --- a/new_pages/r_projects.qmd +++ b/new_pages/r_projects.qmd @@ -2,19 +2,20 @@ # R projects {} -An R project enables your work to be bundled in a portable, self-contained folder. Within the project, all the relevant scripts, data files, figures/outputs, and history are stored in sub-folders and importantly - the *working directory* is the project's root folder. +An R project enables your work to be bundled in a portable, self-contained folder. Within the project, all the relevant scripts, data files, figures/outputs, and history are stored in sub-folders. Importantly - the "working directory" of the R session is the project's root folder. + +Host one discrete work project within one R project (e.g. one outbreak, or analysis). ## Suggested use -A common, efficient, and trouble-free way to use R is to combine these 3 elements. One discrete work project is hosted within one R project. Each element is described in the sections below. +A common, efficient, and trouble-free way to use R is to combine these 3 elements. Each element is described in the sections below. + +1) **An R project** - A self-contained working environment that enables the use of file paths written relative to its root folder. See [Import and export](importing.qmd) for more information. +2) **The *rio* package** - Its functions `import()` and `export()` that handle any file type by its extension (e.g. .csv, .xlsx, .png). +3) **The *here* package** - Its function `here()` allows easy creation of file paths, including within automated reports. + -1) An **R project** - - A self-contained working environment with folders for data, scripts, outputs, etc. -2) The **here** package for relative filepaths - - Filepaths are written relative to the root folder of the R project - see [Import and export](importing.qmd) for more information -3) The **rio** package for importing/exporting - - `import()` and `export()` handle any file type by by its extension (e.g. .csv, .xlsx, .png) diff --git a/new_pages/regression.qmd b/new_pages/regression.qmd index 131b25bf..91057c16 100644 --- a/new_pages/regression.qmd +++ b/new_pages/regression.qmd @@ -6,18 +6,18 @@ This page demonstrates the use of **base** R regression functions such as `glm() look at associations between variables (e.g. odds ratios, risk ratios and hazard ratios). It also uses functions like `tidy()` from the **broom** package to clean-up regression outputs. -1. Univariate: two-by-two tables -2. Stratified: mantel-haenszel estimates -3. Multivariable: variable selection, model selection, final table -4. Forest plots +1. Univariate: two-by-two tables. +2. Stratified: Mantel–Haenszel estimates. +3. Multivariable: variable selection, model selection, final table. +4. Forest plots. For Cox proportional hazard regression, see the [Survival analysis](survival_analysis.qmd) page. -**_NOTE:_** We use the term *multivariable* to refer to a regression with multiple explanatory variables. In this sense a *multivariate* model would be a regression with several outcomes - see this [editorial](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3518362/) for detail +**_NOTE:_** We use the term *multivariable* to refer to a regression with multiple explanatory variables. In this sense a *multivariate* model would be a regression with several outcomes - see this [editorial](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3518362/) for detail . -## Preparation { } +## Preparation {.unnumbered} ### Load packages {.unnumbered} @@ -44,7 +44,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, message=F, warning=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -75,7 +75,7 @@ explanatory_vars <- c("gender", "fever", "chills", "cough", "aches", "vomit") #### Convert to 1's and 0's {.unnumbered} -Below we convert the explanatory columns from "yes"/"no", "m"/"f", and "dead"/"alive" to **1 / 0**, to cooperate with the expectations of logistic regression models. To do this efficiently, used `across()` from **dplyr** to transform multiple columns at one time. The function we apply to each column is `case_when()` (also **dplyr**) which applies logic to convert specified values to 1's and 0's. See sections on `across()` and `case_when()` in the [Cleaning data and core functions page](cleaning.qmd#clean_across)). +Below we convert the explanatory columns from "yes"/"no", "m"/"f", and "dead"/"alive" to **1 / 0**, to cooperate with the expectations of logistic regression models. To do this efficiently, used `across()` from **dplyr** to transform multiple columns at one time. The function we apply to each column is `case_when()` (also **dplyr**) which applies logic to convert specified values to 1's and 0's. See sections on `across()` and `case_when()` in the [Cleaning data and core functions page](cleaning.qmd#clean_across). Note: the "." below represents the column that is being processed by `across()` at that moment. @@ -123,7 +123,7 @@ The number of rows remaining in `linelist` is ` nrow(linelist)`. Just like in the page on [Descriptive tables](tables_descriptive.qmd), your use case will determine which R package you use. We present two options for doing univariate analysis: * Use functions available in **base** R to quickly print results to the console. Use the **broom** package to tidy up the outputs. -* Use the **gtsummary** package to model and get publication-ready outputs +* Use the **gtsummary** package to model and get publication-ready outputs. @@ -191,7 +191,7 @@ See the Resource section at the end of this chapter for more detailed tutorials. #### Logistic regression {.unnumbered} -The function `glm()` from the **stats** package (part of **base** R) is used to fit Generalized Linear Models (GLM). +The function `glm()` from the **stats** package (part of **base** R) is used to fit [Generalized Linear Models (GLM)](https://online.stat.psu.edu/stat504/lesson/6/6.1). `glm()` can be used for univariate and multivariable logistic regression (e.g. to get Odds Ratios). Here are the core parts: @@ -202,7 +202,7 @@ glm(formula, family, data, weights, subset, ...) * `formula = ` The model is provided to `glm()` as an equation, with the outcome on the left and explanatory variables on the right of a tilde `~`. * `family = ` This determines the type of model to run. For logistic regression, use `family = "binomial"`, for poisson use `family = "poisson"`. Other examples are in the table below. -* `data = ` Specify your data frame +* `data = ` Specify your data frame. If necessary, you can also specify the link function via the syntax `family = familytype(link = "linkfunction"))`. You can read more in the documentation about other families and optional arguments such as `weights = ` and `subset = ` (`?glm`). @@ -252,7 +252,7 @@ Here we demonstrate how to combine model outputs with a table of counts. 1) Get the *exponentiated* log odds ratio estimates and confidence intervals by passing the model to `tidy()` and setting `exponentiate = TRUE` and `conf.int = TRUE`. -```{r odds_base_single} +```{r odds_base_single, warning=F, message=F} model <- glm(outcome ~ age_cat, family = "binomial", data = linelist) %>% tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs @@ -295,7 +295,7 @@ Here is what this `counts_table` data frame looks like: DT::datatable(counts_table, rownames = FALSE, options = list(pageLength = nrow(counts_table), scrollX=T), class = 'white-space: nowrap' ) ``` -Now we can bind the `counts_table` and the `model` results together horizontally with `bind_cols()` (**dplyr**). Remember that with `bind_cols()` the rows in the two data frames must be aligned perfectly. In this code, because we are binding within a pipe chain, we use `.` to represent the piped object `counts_table` as we bind it to `model`. To finish the process, we use `select()` to pick the desired columns and their order, and finally apply the **base** R `round()` function across all numeric columns to specify 2 decimal places. +Now we can bind the `counts_table` and the `model` results together horizontally with `bind_cols()` from **dplyr**. Remember that with `bind_cols()` the rows in the two data frames must be aligned perfectly. In this code, because we are binding within a pipe chain, we use `.` to represent the piped object `counts_table` as we bind it to `model`. To finish the process, we use `select()` to pick the desired columns and their order, and finally apply the **base** R `round()` function across all numeric columns to specify 2 decimal places. ```{r, message=F, warning=F} combined <- counts_table %>% # begin with table of counts @@ -417,11 +417,11 @@ DT::datatable(univ_tab_base, rownames = FALSE, options = list(pageLength = 5, sc Below we present the use of `tbl_uvregression()` from the **gtsummary** package. Just like in the page on [Descriptive tables](https://epirhandbook.com/descriptive-tables.html), **gtsummary** functions do a good job of running statistics *and* producing professional-looking outputs. This function produces a table of univariate regression results. -We select only the necessary columns from the `linelist` (explanatory variables and the outcome variable) and pipe them into `tbl_uvregression()`. We are going to run univariate regression on each of the columns we defined as `explanatory_vars` in the data Preparation section (gender, fever, chills, cough, aches, vomit, and age_cat). +We select only the necessary columns from the `linelist` (explanatory variables and the outcome variable) and pipe them into `tbl_uvregression()`. We are going to run univariate regression on each of the columns we defined as `explanatory_vars` in the data Preperation section (gender, fever, chills, cough, aches, vomit, and age_cat). Within the function itself, we provide the `method = ` as `glm` (no quotes), the `y = ` outcome column (`outcome`), specify to `method.args = ` that we want to run logistic regression via `family = binomial`, and we tell it to exponentiate the results. -The output is HTML and contains the counts +The output is HTML and contains the counts: ```{r odds_gt, message=F, warning=F} @@ -439,6 +439,34 @@ univ_tab <- linelist %>% univ_tab ``` +### Cross-tabulation + +The **gtsummary** package also allows us to quickly and easily create tables of counts. This can be useful for quickly summarising the data, and putting it in context with the regression we have carried out. + +```{r kruskal_gt} + +#Carry out our regression +univ_tab <- linelist %>% + dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest + + tbl_uvregression( ## produce univariate table + method = glm, ## define regression want to run (generalised linear model) + y = outcome, ## define outcome variable + method.args = list(family = binomial), ## define what type of glm want to run (logistic) + exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds) + ) + +#Create our cross tabulation +cross_tab <- linelist %>% + dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest + tbl_summary(by = outcome) ## create summary table + +tbl_merge(tbls = list(cross_tab, + univ_tab), + tab_spanner = c("Summary", "Univariate regression")) + +``` + There are many modifications you can make to this table output, such as adjusting the text labels, bolding rows by their p-value, etc. See tutorials [here](http://www.danieldsjoberg.com/gtsummary/articles/tbl_regression.html) and elsewhere online. @@ -447,18 +475,94 @@ There are many modifications you can make to this table output, such as adjustin ## Stratified { } -Stratified analysis is currently still being worked on for **gtsummary**, -this page will be updated in due course. +Here we define stratified regression as the process of carrying out separate regression analyses on different “groups” of data. + +Sometimes in your analysis, you will want to investigate whether or not there are different relationships between an outcome and variables, by different strata. This could be something like, a difference in gender, age group, or source of infection. + +To do this, you will want to split your dataset into the strata of interest. For example, creating two separate datasets of `gender == "f"` and `gender == "m"`, would be done by: +```{r} + +f_linelist <- linelist %>% + filter(gender == 0) %>% ## subset to only where the gender == "f" + dplyr::select(explanatory_vars, outcome) ## select variables of interest + +m_linelist <- linelist %>% + filter(gender == 1) %>% ## subset to only where the gender == "f" + dplyr::select(explanatory_vars, outcome) ## select variables of interest + +``` + +Once this has been done, you can carry out your regression in either **base** R or **gtsummary**. +### **base** R +To carry this out in **base** R, you run two different regressions, one for where `gender == "f"` and `gender == "m"`. + +```{r, warning=F, message=F} + +#Run model for f +f_model <- glm(outcome ~ vomit, family = "binomial", data = f_linelist) %>% + tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs + mutate(across(where(is.numeric), round, digits = 2)) %>% # round all numeric columns + mutate(gender = "f") # create a column which identifies these results as using the f dataset + +#Run model for m +m_model <- glm(outcome ~ vomit, family = "binomial", data = m_linelist) %>% + tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs + mutate(across(where(is.numeric), round, digits = 2)) %>% # round all numeric columns + mutate(gender = "m") # create a column which identifies these results as using the m dataset + +#Combine the results +rbind(f_model, + m_model) +``` + +### **gtsummary** + +The same approach is repeated using **gtsummary**, however it is easier to produce publication ready tables with **gtsummary** and compare the two tables with the function `tbl_merge()`. + +```{r, warning=F, message=F} + +#Run model for f +f_model_gt <- f_linelist %>% + dplyr::select(vomit, outcome) %>% ## select variables of interest + tbl_uvregression( ## produce univariate table + method = glm, ## define regression want to run (generalised linear model) + y = outcome, ## define outcome variable + method.args = list(family = binomial), ## define what type of glm want to run (logistic) + exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds) + ) + +#Run model for m +m_model_gt <- m_linelist %>% + dplyr::select(vomit, outcome) %>% ## select variables of interest + tbl_uvregression( ## produce univariate table + method = glm, ## define regression want to run (generalised linear model) + y = outcome, ## define outcome variable + method.args = list(family = binomial), ## define what type of glm want to run (logistic) + exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds) + ) + +#Combine gtsummary tables +f_and_m_table <- tbl_merge( + tbls = list(f_model_gt, + m_model_gt), + tab_spanner = c("Female", + "Male") +) + +#Print +f_and_m_table + +``` ## Multivariable For multivariable analysis, we again present two approaches: -* `glm()` and `tidy()` -* **gtsummary** package +* `glm()` and `tidy()`. +* **gtsummary** package. The workflow is similar for each and only the last step of pulling together a final table is different. @@ -477,7 +581,7 @@ mv_reg <- glm(outcome ~ gender + fever + chills + cough + aches + vomit + age_ca summary(mv_reg) ``` -If you want to include two variables and an interaction between them you can separate them with an asterisk `*` instead of a `+`. Separate them with a colon `:` if you are only specifying the interaction. For example: +If you want to include two variables and an *interaction* between them you can separate them with an asterisk `*` instead of a `+`. Separate them with a colon `:` if you are only specifying the interaction. For example: ```{r, eval=F} glm(outcome ~ gender + age_cat * fever, family = "binomial", data = linelist) @@ -552,7 +656,7 @@ DT::datatable(mv_tab_base, rownames = FALSE, options = list(pageLength = 10, scr #### Combine with **gtsummary** {.unnumbered} The **gtsummary** package provides the `tbl_regression()` function, which will -take the outputs from a regression (`glm()` in this case) and produce an nice +take the outputs from a regression (`glm()` in this case) and produce a nice summary table. ```{r mv_regression_gt} @@ -582,9 +686,9 @@ tbl_merge( An alternative way of combining the `glm()`/`tidy()` univariate and multivariable outputs is with the **dplyr** join functions. -* Join the univariate results from earlier (`univ_tab_base`, which contains counts) with the tidied multivariable results `mv_tab_base` -* Use `select()` to keep only the columns we want, specify their order, and re-name them -* Use `round()` with two decimal places on all the column that are class Double +* Join the univariate results from earlier (`univ_tab_base`, which contains counts) with the tidied multivariable results `mv_tab_base`. +* Use `select()` to keep only the columns we want, specify their order, and re-name them. +* Use `round()` with two decimal places on all the column that are class Double. ```{r, warning=F, message=F} ## combine univariate and multivariable tables @@ -627,9 +731,9 @@ See the page on [ggplot basics](ggplot_basics.qmd) if you are unfamiliar with th You can build a forest plot with `ggplot()` by plotting elements of the multivariable regression results. Add the layers of the plots using these "geoms": -* estimates with `geom_point()` -* confidence intervals with `geom_errorbar()` -* a vertical line at OR = 1 with `geom_vline()` +* estimates with `geom_point()`. +* confidence intervals with `geom_errorbar()`. +* a vertical line at OR = 1 with `geom_vline()`. Before plotting, you may want to use `fct_relevel()` from the **forcats** package to set the order of the variables/levels on the y-axis. `ggplot()` may display them in alpha-numeric order which would not work well for these age category values ("30" would appear before "5"). See the page on [Factors](factors.qmd) for more details. @@ -683,6 +787,41 @@ final_mv_reg %>% ``` +## Model performance + +Once you have built your regression models, you may want to assess how well the model has fit the data. There are many different approaches to do this, and many different metrics with which to assess your model fit, and how it compares with other model formulations. How you assess your model fit will depend on your model, the data, and the context in which you are conducting your work. + +While there are many different functions, and many different packages, to assess model fit, one package that nicely combines several different metrics and approaches into a single source is the [**performance** package](https://easystats.github.io/performance/). This package allows you to assess model assumptions (such as linearity, homogeneity, highlight outliers, etc.) and check how well the model performs (Akaike Information Criterion values, R2, RMSE, etc) with a few simple functions. + +Unfortunately, we are unable to use this package with **gtsummary**, but it readily accepts objects generated by other packages such as **stats**, **lmerMod** and **tidymodels**. Here we will demonstrate its application using the function `glm()` for a multivariable regression. To do this we can use the function `performance()` to assess model fit, and `compare_perfomrance()` to compare the two models. + +```{r} + +#Load in packages +pacman::p_load(performance) + +#Set up regression models +regression_one <- linelist %>% + select(outcome, gender, fever, chills, cough) %>% + glm(formula = outcome ~ ., + family = binomial) + +regression_two <- linelist %>% + select(outcome, days_onset_hosp, aches, vomit, age_years) %>% + glm(formula = outcome ~ ., + family = binomial) + +#Assess model fit +performance(regression_one) +performance(regression_two) + +#Compare model fit +compare_performance(regression_one, + regression_two) + +``` + +For further reading on the **performance** package, and the model tests you can carry out, see their [github](https://easystats.github.io/performance/index.html). diff --git a/new_pages/regression_files/execute-results/html.json b/new_pages/regression_files/execute-results/html.json new file mode 100644 index 00000000..a319d8b6 --- /dev/null +++ b/new_pages/regression_files/execute-results/html.json @@ -0,0 +1,21 @@ +{ + "hash": "747d81f90a204d35bd7b85d08f706d55", + "result": { + "engine": "knitr", + "markdown": "# Univariate and multivariable regression { }\n\n\n\nThis page demonstrates the use of **base** R regression functions such as `glm()` and the **gtsummary** package to \nlook at associations between variables (e.g. odds ratios, risk ratios and hazard\nratios). It also uses functions like `tidy()` from the **broom** package to clean-up regression outputs. \n\n1. Univariate: two-by-two tables. \n2. Stratified: Mantel–Haenszel estimates. \n3. Multivariable: variable selection, model selection, final table.\n4. Forest plots.\n\nFor Cox proportional hazard regression, see the [Survival analysis](survival_analysis.qmd) page. \n\n**_NOTE:_** We use the term *multivariable* to refer to a regression with multiple explanatory variables. In this sense a *multivariate* model would be a regression with several outcomes - see this [editorial](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3518362/) for detail . \n\n\n\n## Preparation { }\n\n\n### Load packages {.unnumbered}\n\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. \n\n\n::: {.cell}\n\n```{.r .cell-code}\npacman::p_load(\n rio, # File import\n here, # File locator\n tidyverse, # data management + ggplot2 graphics, \n stringr, # manipulate text strings \n purrr, # loop over objects in a tidy way\n gtsummary, # summary statistics and tests \n broom, # tidy up results from regressions\n lmtest, # likelihood-ratio tests\n parameters, # alternative to tidy up results from regressions\n see # alternative to visualise forest plots\n )\n```\n:::\n\n\n### Import data {.unnumbered}\n\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the \"clean\" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). \n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: The `trust` argument of `import()` should be explicit for serialization formats\nas of rio 1.0.3.\nℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\nℹ The deprecated feature was likely used in the rio package.\n Please report the issue at .\n```\n\n\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n```\n:::\n\n\nThe first 50 rows of the linelist are displayed below.\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n### Clean data {.unnumbered}\n\n#### Store explanatory variables {.unnumbered} \n\nWe store the names of the explanatory columns as a character vector. This will be referenced later. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## define variables of interest \nexplanatory_vars <- c(\"gender\", \"fever\", \"chills\", \"cough\", \"aches\", \"vomit\")\n```\n:::\n\n\n\n#### Convert to 1's and 0's {.unnumbered} \n\nBelow we convert the explanatory columns from \"yes\"/\"no\", \"m\"/\"f\", and \"dead\"/\"alive\" to **1 / 0**, to cooperate with the expectations of logistic regression models. To do this efficiently, used `across()` from **dplyr** to transform multiple columns at one time. The function we apply to each column is `case_when()` (also **dplyr**) which applies logic to convert specified values to 1's and 0's. See sections on `across()` and `case_when()` in the [Cleaning data and core functions page](cleaning.qmd#clean_across). \n\nNote: the \".\" below represents the column that is being processed by `across()` at that moment.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n## convert dichotomous variables to 0/1 \nlinelist <- linelist %>% \n mutate(across( \n .cols = all_of(c(explanatory_vars, \"outcome\")), ## for each column listed and \"outcome\"\n .fns = ~case_when( \n . %in% c(\"m\", \"yes\", \"Death\") ~ 1, ## recode male, yes and death to 1\n . %in% c(\"f\", \"no\", \"Recover\") ~ 0, ## female, no and recover to 0\n TRUE ~ NA_real_) ## otherwise set to missing\n )\n )\n```\n:::\n\n\n#### Drop rows with missing values {.unnumbered} \n\nTo drop rows with missing values, can use the **tidyr** function `drop_na()`. However, we only want to do this for rows that are missing values in the columns of interest. \n\nThe first thing we must to is make sure our `explanatory_vars` vector includes the column `age` (`age` would have produced an error in the previous `case_when()` operation, which was only for dichotomous variables). Then we pipe the `linelist` to `drop_na()` to remove any rows with missing values in the `outcome` column or any of the `explanatory_vars` columns. \n\nBefore running the code, the number of rows in the `linelist` is ` nrow(linelist)`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## add in age_category to the explanatory vars \nexplanatory_vars <- c(explanatory_vars, \"age_cat\")\n\n## drop rows with missing information for variables of interest \nlinelist <- linelist %>% \n drop_na(any_of(c(\"outcome\", explanatory_vars)))\n```\n:::\n\n\nThe number of rows remaining in `linelist` is ` nrow(linelist)`. \n\n\n\n\n## Univariate { }\n\nJust like in the page on [Descriptive tables](tables_descriptive.qmd), your use case will determine which R package you use. We present two options for doing univariate analysis: \n\n* Use functions available in **base** R to quickly print results to the console. Use the **broom** package to tidy up the outputs. \n* Use the **gtsummary** package to model and get publication-ready outputs. \n\n\n\n\n\n### **base** R {.unnumbered}\n\n#### Linear regression {.unnumbered} \n\nThe **base** R function `lm()` perform linear regression, assessing the relationship between numeric response and explanatory variables that are assumed to have a linear relationship. \n\nProvide the equation as a formula, with the response and explanatory column names separated by a tilde `~`. Also, specify the dataset to `data = `. Define the model results as an R object, to use later. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlm_results <- lm(ht_cm ~ age, data = linelist)\n```\n:::\n\n\nYou can then run `summary()` on the model results to see the coefficients (Estimates), P-value, residuals, and other measures. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(lm_results)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\nCall:\nlm(formula = ht_cm ~ age, data = linelist)\n\nResiduals:\n Min 1Q Median 3Q Max \n-128.579 -15.854 1.177 15.887 175.483 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 69.9051 0.5979 116.9 <2e-16 ***\nage 3.4354 0.0293 117.2 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 23.75 on 4165 degrees of freedom\nMultiple R-squared: 0.7675,\tAdjusted R-squared: 0.7674 \nF-statistic: 1.375e+04 on 1 and 4165 DF, p-value: < 2.2e-16\n```\n\n\n:::\n:::\n\n\nAlternatively you can use the `tidy()` function from the **broom** package to pull \nthe results in to a table. What the results tell us is that for each year increase in age the height increases\nby 3.5 cm and this is statistically significant. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(lm_results)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 69.9 0.598 117. 0\n2 age 3.44 0.0293 117. 0\n```\n\n\n:::\n:::\n\n\nYou can then also use this regression to add it to a **ggplot**, to do this we \nfirst pull the points for the observed data and the fitted line in to one data frame \nusing the `augment()` function from **broom**. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## pull the regression points and observed data in to one dataset\npoints <- augment(lm_results)\n\n## plot the data using age as the x-axis \nggplot(points, aes(x = age)) + \n ## add points for height \n geom_point(aes(y = ht_cm)) + \n ## add your regression line \n geom_line(aes(y = .fitted), colour = \"red\")\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-html/lin_reg_res_plot-1.png){width=672}\n:::\n:::\n\n\nIt is also possible to add a simple linear regression straight straight in **ggplot** \nusing the `geom_smooth()` function. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## add your data to a plot \n ggplot(linelist, aes(x = age, y = ht_cm)) + \n ## show points\n geom_point() + \n ## add a linear regression \n geom_smooth(method = \"lm\", se = FALSE)\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n`geom_smooth()` using formula = 'y ~ x'\n```\n\n\n:::\n\n::: {.cell-output-display}\n![](regression_files/figure-html/geom_smooth-1.png){width=672}\n:::\n:::\n\n\nSee the Resource section at the end of this chapter for more detailed tutorials. \n\n\n#### Logistic regression {.unnumbered} \n\nThe function `glm()` from the **stats** package (part of **base** R) is used to fit [Generalized Linear Models (GLM)](https://online.stat.psu.edu/stat504/lesson/6/6.1). \n\n`glm()` can be used for univariate and multivariable logistic regression (e.g. to get Odds Ratios). Here are the core parts: \n\n\n::: {.cell}\n\n```{.r .cell-code}\n# arguments for glm()\nglm(formula, family, data, weights, subset, ...)\n```\n:::\n\n\n* `formula = ` The model is provided to `glm()` as an equation, with the outcome on the left and explanatory variables on the right of a tilde `~`. \n* `family = ` This determines the type of model to run. For logistic regression, use `family = \"binomial\"`, for poisson use `family = \"poisson\"`. Other examples are in the table below. \n* `data = ` Specify your data frame. \n\n\nIf necessary, you can also specify the link function via the syntax `family = familytype(link = \"linkfunction\"))`. You can read more in the documentation about other families and optional arguments such as `weights = ` and `subset = ` (`?glm`). \n\n\n\nFamily | Default link function \n-----------------------|------------------------------------------- \n`\"binomial\"` | `(link = \"logit\")` \n`\"gaussian\"` | `(link = \"identity\")` \n`\"Gamma\"` | `(link = \"inverse\")` \n`\"inverse.gaussian\"` | `(link = \"1/mu^2\")` \n`\"poisson\"` | `(link = \"log\")` \n`\"quasi\"` | `(link = \"identity\", variance = \"constant\")` \n`\"quasibinomial\"` | `(link = \"logit\")` \n`\"quasipoisson\"` | `(link = \"log\")` \n\n\nWhen running `glm()` it is most common to save the results as a named R object. Then you can print the results to your console using `summary()` as shown below, or perform other operations on the results (e.g. exponentiate). \n\nIf you need to run a negative binomial regression you can use the **MASS** package; the `glm.nb()` uses the same syntax as `glm()`. \nFor a walk-through of different regressions, see the [UCLA stats page](https://stats.idre.ucla.edu/other/dae/). \n\n#### Univariate `glm()` {.unnumbered}\n\nIn this example we are assessing the association between different age categories and the outcome of death (coded as 1 in the Preparation section). Below is a univariate model of `outcome` by `age_cat`. We save the model output as `model` and then print it with `summary()` to the console. Note the estimates provided are the *log odds* and that the baseline level is the first factor level of `age_cat` (\"0-4\"). \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmodel <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist)\nsummary(model)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\nCall:\nglm(formula = outcome ~ age_cat, family = \"binomial\", data = linelist)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 0.233738 0.072805 3.210 0.00133 **\nage_cat5-9 -0.062898 0.101733 -0.618 0.53640 \nage_cat10-14 0.138204 0.107186 1.289 0.19726 \nage_cat15-19 -0.005565 0.113343 -0.049 0.96084 \nage_cat20-29 0.027511 0.102133 0.269 0.78765 \nage_cat30-49 0.063764 0.113771 0.560 0.57517 \nage_cat50-69 -0.387889 0.259240 -1.496 0.13459 \nage_cat70+ -0.639203 0.915770 -0.698 0.48518 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5705.1 on 4159 degrees of freedom\nAIC: 5721.1\n\nNumber of Fisher Scoring iterations: 4\n```\n\n\n:::\n:::\n\n\nTo alter the baseline level of a given variable, ensure the column is class Factor and move the desired level to the first position with `fct_relevel()` (see page on [Factors](factors.qmd)). For example, below we take column `age_cat` and set \"20-29\" as the baseline before piping the modified data frame into `glm()`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n mutate(age_cat = fct_relevel(age_cat, \"20-29\", after = 0)) %>% \n glm(formula = outcome ~ age_cat, family = \"binomial\") %>% \n summary()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\nCall:\nglm(formula = outcome ~ age_cat, family = \"binomial\", data = .)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 0.26125 0.07163 3.647 0.000265 ***\nage_cat0-4 -0.02751 0.10213 -0.269 0.787652 \nage_cat5-9 -0.09041 0.10090 -0.896 0.370220 \nage_cat10-14 0.11069 0.10639 1.040 0.298133 \nage_cat15-19 -0.03308 0.11259 -0.294 0.768934 \nage_cat30-49 0.03625 0.11302 0.321 0.748390 \nage_cat50-69 -0.41540 0.25891 -1.604 0.108625 \nage_cat70+ -0.66671 0.91568 -0.728 0.466546 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5705.1 on 4159 degrees of freedom\nAIC: 5721.1\n\nNumber of Fisher Scoring iterations: 4\n```\n\n\n:::\n:::\n\n\n#### Printing results {.unnumbered}\n\nFor most uses, several modifications must be made to the above outputs. The function `tidy()` from the package **broom** is convenient for making the model results presentable. \n\nHere we demonstrate how to combine model outputs with a table of counts. \n\n1) Get the *exponentiated* log odds ratio estimates and confidence intervals by passing the model to `tidy()` and setting `exponentiate = TRUE` and `conf.int = TRUE`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmodel <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist) %>% \n tidy(exponentiate = TRUE, conf.int = TRUE) %>% # exponentiate and produce CIs\n mutate(across(where(is.numeric), round, digits = 2)) # round all numeric columns\n```\n:::\n\n\nBelow is the outputted tibble `model`: \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n2) Combine these model results with a table of counts. Below, we create the a counts cross-table with the `tabyl()` function from **janitor**, as covered in the [Descriptive tables](tables_descriptive.qmd) page. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncounts_table <- linelist %>% \n janitor::tabyl(age_cat, outcome)\n```\n:::\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nHere is what this `counts_table` data frame looks like: \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\nNow we can bind the `counts_table` and the `model` results together horizontally with `bind_cols()` (**dplyr**). Remember that with `bind_cols()` the rows in the two data frames must be aligned perfectly. In this code, because we are binding within a pipe chain, we use `.` to represent the piped object `counts_table` as we bind it to `model`. To finish the process, we use `select()` to pick the desired columns and their order, and finally apply the **base** R `round()` function across all numeric columns to specify 2 decimal places. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncombined <- counts_table %>% # begin with table of counts\n bind_cols(., model) %>% # combine with the outputs of the regression \n select(term, 2:3, estimate, # select and re-order cols\n conf.low, conf.high, p.value) %>% \n mutate(across(where(is.numeric), round, digits = 2)) ## round to 2 decimal places\n```\n:::\n\n\nHere is what the combined data frame looks like, printed nicely as an image with a function from **flextable**. The [Tables for presentation](tables_presentation.qmd) explains how to customize such tables with **flextable**, or or you can use numerous other packages such as **knitr** or **GT**. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncombined <- combined %>% \n flextable::qflextable()\n```\n:::\n\n\n\n#### Looping multiple univariate models {.unnumbered} \n\nBelow we present a method using `glm()` and `tidy()` for a more simple approach, see the section on **gtsummary**. \n\nTo run the models on several exposure variables to produce univariate odds ratios (i.e. not controlling for each other), you can use the approach below. It uses `str_c()` from **stringr** to create univariate formulas (see [Characters and strings](characters_strings.qmd)), runs the `glm()` regression on each formula, passes each `glm()` output to `tidy()` and finally collapses all the model outputs together with `bind_rows()` from **tidyr**. This approach uses `map()` from the package **purrr** to iterate - see the page on [Iteration, loops, and lists](iteration.qmd) for more information on this tool. \n\n1) Create a vector of column names of the explanatory variables. We already have this as `explanatory_vars` from the Preparation section of this page. \n\n2) Use `str_c()` to create multiple string formulas, with `outcome` on the left, and a column name from `explanatory_vars` on the right. The period `.` substitutes for the column name in `explanatory_vars`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nexplanatory_vars %>% str_c(\"outcome ~ \", .)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"outcome ~ gender\" \"outcome ~ fever\" \"outcome ~ chills\" \n[4] \"outcome ~ cough\" \"outcome ~ aches\" \"outcome ~ vomit\" \n[7] \"outcome ~ age_cat\"\n```\n\n\n:::\n:::\n\n\n3) Pass these string formulas to `map()` and set `~glm()` as the function to apply to each input. Within `glm()`, set the regression formula as `as.formula(.x)` where `.x` will be replaced by the string formula defined in the step above. `map()` will loop over each of the string formulas, running regressions for each one. \n\n4) The outputs of this first `map()` are passed to a second `map()` command, which applies `tidy()` to the regression outputs. \n\n5) Finally the output of the second `map()` (a list of tidied data frames) is condensed with `bind_rows()`, resulting in one data frame with all the univariate results. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmodels <- explanatory_vars %>% # begin with variables of interest\n str_c(\"outcome ~ \", .) %>% # combine each variable into formula (\"outcome ~ variable of interest\")\n \n # iterate through each univariate formula\n map( \n .f = ~glm( # pass the formulas one-by-one to glm()\n formula = as.formula(.x), # within glm(), the string formula is .x\n family = \"binomial\", # specify type of glm (logistic)\n data = linelist)) %>% # dataset\n \n # tidy up each of the glm regression outputs from above\n map(\n .f = ~tidy(\n .x, \n exponentiate = TRUE, # exponentiate \n conf.int = TRUE)) %>% # return confidence intervals\n \n # collapse the list of regression outputs in to one data frame\n bind_rows() %>% \n \n # round all numeric columns\n mutate(across(where(is.numeric), round, digits = 2))\n```\n:::\n\n\nThis time, the end object `models` is longer because it now represents the combined results of several univariate regressions. Click through to see all the rows of `model`. \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\nAs before, we can create a counts table from the `linelist` for each explanatory variable, bind it to `models`, and make a nice table. We begin with the variables, and iterate through them with `map()`. We iterate through a user-defined function which involves creating a counts table with **dplyr** functions. Then the results are combined and bound with the `models` model results. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n## for each explanatory variable\nuniv_tab_base <- explanatory_vars %>% \n map(.f = \n ~{linelist %>% ## begin with linelist\n group_by(outcome) %>% ## group data set by outcome\n count(.data[[.x]]) %>% ## produce counts for variable of interest\n pivot_wider( ## spread to wide format (as in cross-tabulation)\n names_from = outcome,\n values_from = n) %>% \n drop_na(.data[[.x]]) %>% ## drop rows with missings\n rename(\"variable\" = .x) %>% ## change variable of interest column to \"variable\"\n mutate(variable = as.character(variable))} ## convert to character, else non-dichotomous (categorical) variables come out as factor and cant be merged\n ) %>% \n \n ## collapse the list of count outputs in to one data frame\n bind_rows() %>% \n \n ## merge with the outputs of the regression \n bind_cols(., models) %>% \n \n ## only keep columns interested in \n select(term, 2:3, estimate, conf.low, conf.high, p.value) %>% \n \n ## round decimal places\n mutate(across(where(is.numeric), round, digits = 2))\n```\n:::\n\n\nBelow is what the data frame looks like. See the page on [Tables for presentation](tables_descriptive.qmd) for ideas on how to further convert this table to pretty HTML output (e.g. with **flextable**). \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n\n\n\n\n\n\n### **gtsummary** package {#reg_gt_uni .unnumbered}\n\nBelow we present the use of `tbl_uvregression()` from the **gtsummary** package. Just like in the page on [Descriptive tables](https://epirhandbook.com/descriptive-tables.html), **gtsummary** functions do a good job of running statistics *and* producing professional-looking outputs. This function produces a table of univariate regression results. \n\nWe select only the necessary columns from the `linelist` (explanatory variables and the outcome variable) and pipe them into `tbl_uvregression()`. We are going to run univariate regression on each of the columns we defined as `explanatory_vars` in the data Preparation section (gender, fever, chills, cough, aches, vomit, and age_cat). \n\nWithin the function itself, we provide the `method = ` as `glm` (no quotes), the `y = ` outcome column (`outcome`), specify to `method.args = ` that we want to run logistic regression via `family = binomial`, and we tell it to exponentiate the results. \n\nThe output is HTML and contains the counts\n\n\n::: {.cell}\n\n```{.r .cell-code}\nuniv_tab <- linelist %>% \n dplyr::select(explanatory_vars, outcome) %>% ## select variables of interest\n\n tbl_uvregression( ## produce univariate table\n method = glm, ## define regression want to run (generalised linear model)\n y = outcome, ## define outcome variable\n method.args = list(family = binomial), ## define what type of glm want to run (logistic)\n exponentiate = TRUE ## exponentiate to produce odds ratios (rather than log odds)\n )\n\n## view univariate results table \nuniv_tab\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n\n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n

    N

    \n

    OR

    \n
    1

    95% CI

    \n
    1

    p-value

    \n
    gender4,1671.000.88, 1.13>0.9
    fever4,1671.000.85, 1.17>0.9
    chills4,1671.030.89, 1.210.7
    cough4,1671.150.97, 1.370.11
    aches4,1670.930.76, 1.140.5
    vomit4,1671.090.96, 1.230.2
    age_cat4,167


        0-4

        5-9
    0.940.77, 1.150.5
        10-14
    1.150.93, 1.420.2
        15-19
    0.990.80, 1.24>0.9
        20-29
    1.030.84, 1.260.8
        30-49
    1.070.85, 1.330.6
        50-69
    0.680.41, 1.130.13
        70+
    0.530.07, 3.200.5
    1

    OR = Odds Ratio, CI = Confidence Interval

    \n
    \n
    \n```\n\n:::\n:::\n\n\nThere are many modifications you can make to this table output, such as adjusting the text labels, bolding rows by their p-value, etc. See tutorials [here](http://www.danieldsjoberg.com/gtsummary/articles/tbl_regression.html) and elsewhere online. \n\n\n\n\n\n## Stratified { }\n\nStratified analysis is currently still being worked on for **gtsummary**, \nthis page will be updated in due course. \n\n\n\n\n## Multivariable \n\nFor multivariable analysis, we again present two approaches: \n\n* `glm()` and `tidy()` \n* **gtsummary** package \n\nThe workflow is similar for each and only the last step of pulling together a final table is different.\n\n\n### Conduct multivariable {.unnumbered} \n\n\nHere we use `glm()` but add more variables to the right side of the equation, separated by plus symbols (`+`). \n\n\nTo run the model with all of our explanatory variables we would run: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmv_reg <- glm(outcome ~ gender + fever + chills + cough + aches + vomit + age_cat, family = \"binomial\", data = linelist)\n\nsummary(mv_reg)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\nCall:\nglm(formula = outcome ~ gender + fever + chills + cough + aches + \n vomit + age_cat, family = \"binomial\", data = linelist)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 0.069054 0.131726 0.524 0.600\ngender 0.002448 0.065133 0.038 0.970\nfever 0.004309 0.080522 0.054 0.957\nchills 0.034112 0.078924 0.432 0.666\ncough 0.138584 0.089909 1.541 0.123\naches -0.070705 0.104078 -0.679 0.497\nvomit 0.086098 0.062618 1.375 0.169\nage_cat5-9 -0.063562 0.101851 -0.624 0.533\nage_cat10-14 0.136372 0.107275 1.271 0.204\nage_cat15-19 -0.011074 0.113640 -0.097 0.922\nage_cat20-29 0.026552 0.102780 0.258 0.796\nage_cat30-49 0.059569 0.116402 0.512 0.609\nage_cat50-69 -0.388964 0.262384 -1.482 0.138\nage_cat70+ -0.647443 0.917375 -0.706 0.480\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 5712.4 on 4166 degrees of freedom\nResidual deviance: 5700.2 on 4153 degrees of freedom\nAIC: 5728.2\n\nNumber of Fisher Scoring iterations: 4\n```\n\n\n:::\n:::\n\n\nIf you want to include two variables and an interaction between them you can separate them with an asterisk `*` instead of a `+`. Separate them with a colon `:` if you are only specifying the interaction. For example: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nglm(outcome ~ gender + age_cat * fever, family = \"binomial\", data = linelist)\n```\n:::\n\n\n\n*Optionally*, you can use this code to leverage the pre-defined vector of column names and re-create the above command using `str_c()`. This might be useful if your explanatory variable names are changing, or you don't want to type them all out again. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## run a regression with all variables of interest \nmv_reg <- explanatory_vars %>% ## begin with vector of explanatory column names\n str_c(collapse = \"+\") %>% ## combine all names of the variables of interest separated by a plus\n str_c(\"outcome ~ \", .) %>% ## combine the names of variables of interest with outcome in formula style\n glm(family = \"binomial\", ## define type of glm as logistic,\n data = linelist) ## define your dataset\n```\n:::\n\n\n\n#### Building the model {.unnumbered} \n\nYou can build your model step-by-step, saving various models that include certain explanatory variables. You can compare these models with likelihood-ratio tests using `lrtest()` from the package **lmtest**, as below: \n\n**_NOTE:_** Using **base** `anova(model1, model2, test = \"Chisq)` produces the same results \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmodel1 <- glm(outcome ~ age_cat, family = \"binomial\", data = linelist)\nmodel2 <- glm(outcome ~ age_cat + gender, family = \"binomial\", data = linelist)\n\nlmtest::lrtest(model1, model2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nLikelihood ratio test\n\nModel 1: outcome ~ age_cat\nModel 2: outcome ~ age_cat + gender\n #Df LogLik Df Chisq Pr(>Chisq)\n1 8 -2852.6 \n2 9 -2852.6 1 2e-04 0.9883\n```\n\n\n:::\n:::\n\n\nAnother option is to take the model object and apply the `step()` function from the **stats** package. Specify which variable selection direction you want use when building the model. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## choose a model using forward selection based on AIC\n## you can also do \"backward\" or \"both\" by adjusting the direction\nfinal_mv_reg <- mv_reg %>%\n step(direction = \"forward\", trace = FALSE)\n```\n:::\n\n\n\nYou can also turn off scientific notation in your R session, for clarity: \n\n\n::: {.cell}\n\n```{.r .cell-code}\noptions(scipen=999)\n```\n:::\n\n\nAs described in the section on univariate analysis, pass the model output to `tidy()` to exponentiate the log odds and CIs. Finally we round all numeric columns to two decimal places. Scroll through to see all the rows. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmv_tab_base <- final_mv_reg %>% \n broom::tidy(exponentiate = TRUE, conf.int = TRUE) %>% ## get a tidy dataframe of estimates \n mutate(across(where(is.numeric), round, digits = 2)) ## round \n```\n:::\n\n\nHere is what the resulting data frame looks like: \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n\n\n\n\n\n\n### Combine univariate and multivariable {.unnumbered}\n\n#### Combine with **gtsummary** {.unnumbered} \n\nThe **gtsummary** package provides the `tbl_regression()` function, which will \ntake the outputs from a regression (`glm()` in this case) and produce an nice \nsummary table. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## show results table of final regression \nmv_tab <- tbl_regression(final_mv_reg, exponentiate = TRUE)\n```\n:::\n\n\nLet's see the table: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmv_tab\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n

    OR

    \n
    1

    95% CI

    \n
    1

    p-value

    \n
    gender1.000.88, 1.14>0.9
    fever1.000.86, 1.18>0.9
    chills1.030.89, 1.210.7
    cough1.150.96, 1.370.12
    aches0.930.76, 1.140.5
    vomit1.090.96, 1.230.2
    age_cat


        0-4
        5-90.940.77, 1.150.5
        10-141.150.93, 1.410.2
        15-190.990.79, 1.24>0.9
        20-291.030.84, 1.260.8
        30-491.060.85, 1.330.6
        50-690.680.40, 1.130.14
        70+0.520.07, 3.190.5
    1

    OR = Odds Ratio, CI = Confidence Interval

    \n
    \n
    \n```\n\n:::\n:::\n\n\nYou can also combine several different output tables produced by **gtsummary** with \nthe `tbl_merge()` function. We now combine the multivariable results with the **gtsummary** *univariate* results that we created [above](#reg_gt_uni): \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## combine with univariate results \ntbl_merge(\n tbls = list(univ_tab, mv_tab), # combine\n tab_spanner = c(\"**Univariate**\", \"**Multivariable**\")) # set header names\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n
    \n

    Univariate

    \n
    \n
    \n

    Multivariable

    \n
    \n

    N

    \n

    OR

    \n
    1

    95% CI

    \n
    1

    p-value

    \n

    OR

    \n
    1

    95% CI

    \n
    1

    p-value

    \n
    gender4,1671.000.88, 1.13>0.91.000.88, 1.14>0.9
    fever4,1671.000.85, 1.17>0.91.000.86, 1.18>0.9
    chills4,1671.030.89, 1.210.71.030.89, 1.210.7
    cough4,1671.150.97, 1.370.111.150.96, 1.370.12
    aches4,1670.930.76, 1.140.50.930.76, 1.140.5
    vomit4,1671.090.96, 1.230.21.090.96, 1.230.2
    age_cat4,167





        0-4


        5-9
    0.940.77, 1.150.50.940.77, 1.150.5
        10-14
    1.150.93, 1.420.21.150.93, 1.410.2
        15-19
    0.990.80, 1.24>0.90.990.79, 1.24>0.9
        20-29
    1.030.84, 1.260.81.030.84, 1.260.8
        30-49
    1.070.85, 1.330.61.060.85, 1.330.6
        50-69
    0.680.41, 1.130.130.680.40, 1.130.14
        70+
    0.530.07, 3.200.50.520.07, 3.190.5
    1

    OR = Odds Ratio, CI = Confidence Interval

    \n
    \n
    \n```\n\n:::\n:::\n\n\n\n\n#### Combine with **dplyr** {.unnumbered} \n\nAn alternative way of combining the `glm()`/`tidy()` univariate and multivariable outputs is with the **dplyr** join functions. \n\n* Join the univariate results from earlier (`univ_tab_base`, which contains counts) with the tidied multivariable results `mv_tab_base` \n* Use `select()` to keep only the columns we want, specify their order, and re-name them \n* Use `round()` with two decimal places on all the column that are class Double \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## combine univariate and multivariable tables \nleft_join(univ_tab_base, mv_tab_base, by = \"term\") %>% \n ## choose columns and rename them\n select( # new name = old name\n \"characteristic\" = term, \n \"recovered\" = \"0\", \n \"dead\" = \"1\", \n \"univ_or\" = estimate.x, \n \"univ_ci_low\" = conf.low.x, \n \"univ_ci_high\" = conf.high.x,\n \"univ_pval\" = p.value.x, \n \"mv_or\" = estimate.y, \n \"mvv_ci_low\" = conf.low.y, \n \"mv_ci_high\" = conf.high.y,\n \"mv_pval\" = p.value.y \n ) %>% \n mutate(across(where(is.double), round, 2)) \n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 20 × 11\n characteristic recovered dead univ_or univ_ci_low univ_ci_high univ_pval\n \n 1 (Intercept) 909 1168 1.28 1.18 1.4 0 \n 2 gender 916 1174 1 0.88 1.13 0.97\n 3 (Intercept) 340 436 1.28 1.11 1.48 0 \n 4 fever 1485 1906 1 0.85 1.17 0.99\n 5 (Intercept) 1472 1877 1.28 1.19 1.37 0 \n 6 chills 353 465 1.03 0.89 1.21 0.68\n 7 (Intercept) 272 309 1.14 0.97 1.34 0.13\n 8 cough 1553 2033 1.15 0.97 1.37 0.11\n 9 (Intercept) 1636 2114 1.29 1.21 1.38 0 \n10 aches 189 228 0.93 0.76 1.14 0.51\n11 (Intercept) 931 1144 1.23 1.13 1.34 0 \n12 vomit 894 1198 1.09 0.96 1.23 0.17\n13 (Intercept) 338 427 1.26 1.1 1.46 0 \n14 age_cat5-9 365 433 0.94 0.77 1.15 0.54\n15 age_cat10-14 273 396 1.15 0.93 1.42 0.2 \n16 age_cat15-19 238 299 0.99 0.8 1.24 0.96\n17 age_cat20-29 345 448 1.03 0.84 1.26 0.79\n18 age_cat30-49 228 307 1.07 0.85 1.33 0.58\n19 age_cat50-69 35 30 0.68 0.41 1.13 0.13\n20 age_cat70+ 3 2 0.53 0.07 3.2 0.49\n# ℹ 4 more variables: mv_or , mvv_ci_low , mv_ci_high ,\n# mv_pval \n```\n\n\n:::\n:::\n\n\n\n\n\n\n\n## Forest plot { }\n\nThis section shows how to produce a plot with the outputs of your regression.\nThere are two options, you can build a plot yourself using **ggplot2** or use a \nmeta-package called **easystats** (a package that includes many packages). \n\nSee the page on [ggplot basics](ggplot_basics.qmd) if you are unfamiliar with the **ggplot2** plotting package. \n\n\n\n\n### **ggplot2** package {.unnumbered}\n\nYou can build a forest plot with `ggplot()` by plotting elements of the multivariable regression results. Add the layers of the plots using these \"geoms\": \n\n* estimates with `geom_point()` \n* confidence intervals with `geom_errorbar()` \n* a vertical line at OR = 1 with `geom_vline()` \n\nBefore plotting, you may want to use `fct_relevel()` from the **forcats** package to set the order of the variables/levels on the y-axis. `ggplot()` may display them in alpha-numeric order which would not work well for these age category values (\"30\" would appear before \"5\"). See the page on [Factors](factors.qmd) for more details. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## remove the intercept term from your multivariable results\nmv_tab_base %>% \n \n #set order of levels to appear along y-axis\n mutate(term = fct_relevel(\n term,\n \"vomit\", \"gender\", \"fever\", \"cough\", \"chills\", \"aches\",\n \"age_cat5-9\", \"age_cat10-14\", \"age_cat15-19\", \"age_cat20-29\",\n \"age_cat30-49\", \"age_cat50-69\", \"age_cat70+\")) %>%\n \n # remove \"intercept\" row from plot\n filter(term != \"(Intercept)\") %>% \n \n ## plot with variable on the y axis and estimate (OR) on the x axis\n ggplot(aes(x = estimate, y = term)) +\n \n ## show the estimate as a point\n geom_point() + \n \n ## add in an error bar for the confidence intervals\n geom_errorbar(aes(xmin = conf.low, xmax = conf.high)) + \n \n ## show where OR = 1 is for reference as a dashed line\n geom_vline(xintercept = 1, linetype = \"dashed\")\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-html/ggplot_forest-1.png){width=672}\n:::\n:::\n\n\n\n\n\n### **easystats** packages {.unnumbered}\n\nAn alternative, if you do not want to the fine level of control that **ggplot2** provides, is to use a combination of **easystats** packages. \n\nThe function `model_parameters()` from the **parameters** package does the equivalent\nof the **broom** package function `tidy()`. The **see** package then accepts those outputs\nand creates a default forest plot as a `ggplot()` object. \n\n\n::: {.cell}\n\n```{.r .cell-code}\npacman::p_load(easystats)\n\n## remove the intercept term from your multivariable results\nfinal_mv_reg %>% \n model_parameters(exponentiate = TRUE) %>% \n plot()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-html/easystats_forest-1.png){width=672}\n:::\n:::\n\n\n\n\n\n## Resources { }\n\nThe content of this page was informed by these resources and vignettes online: \n\n[Linear regression in R](https://www.datacamp.com/community/tutorials/linear-regression-R) \n\n[gtsummary](http://www.danieldsjoberg.com/gtsummary/articles/tbl_regression.html) \n\n[UCLA stats page](https://stats.idre.ucla.edu/other/dae/) \n\n[sthda stepwise regression](http://www.sthda.com/english/articles/36-classification-methods-essentials/150-stepwise-logistic-regression-essentials-in-r/) \n\n", + "supporting": [ + "regression_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-in-header": [ + "\n\n\n\n\n\n\n\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/new_pages/regression_files/figure-html/easystats_forest-1.png b/new_pages/regression_files/figure-html/easystats_forest-1.png new file mode 100644 index 00000000..c7cb391d Binary files /dev/null and b/new_pages/regression_files/figure-html/easystats_forest-1.png differ diff --git a/new_pages/regression_files/figure-html/geom_smooth-1.png b/new_pages/regression_files/figure-html/geom_smooth-1.png new file mode 100644 index 00000000..b6d63b2d Binary files /dev/null and b/new_pages/regression_files/figure-html/geom_smooth-1.png differ diff --git a/new_pages/regression_files/figure-html/ggplot_forest-1.png b/new_pages/regression_files/figure-html/ggplot_forest-1.png new file mode 100644 index 00000000..c66aea52 Binary files /dev/null and b/new_pages/regression_files/figure-html/ggplot_forest-1.png differ diff --git a/new_pages/regression_files/figure-html/lin_reg_res_plot-1.png b/new_pages/regression_files/figure-html/lin_reg_res_plot-1.png new file mode 100644 index 00000000..96fefc14 Binary files /dev/null and b/new_pages/regression_files/figure-html/lin_reg_res_plot-1.png differ diff --git a/new_pages/reportfactory.qmd b/new_pages/reportfactory.qmd index 2a9b3092..4425d634 100644 --- a/new_pages/reportfactory.qmd +++ b/new_pages/reportfactory.qmd @@ -19,7 +19,6 @@ You can do this via the **pacman** package with `p_load_current_gh()` which will ```{r, eval=FALSE} # Install and load the latest version of the package from Github pacman::p_load_current_gh("reconverse/reportfactory") -#remotes::install_github("reconverse/reportfactory") # alternative ``` @@ -27,9 +26,8 @@ pacman::p_load_current_gh("reconverse/reportfactory") To create a new factory, run the function `new_factory()`. This will create a new self-contained R project folder. By default: -* The factory will be added to your working directory -* The name of the factory R project will be called "new_factory.Rproj" -* Your RStudio session will "move in" to this R project +* The factory will be added to your working directory. +* The name of the factory R project will be called "new_factory.Rproj". * Your RStudio session will "move in" to this R project. ```{r, eval=F} # This will create the factory in the working directory @@ -43,23 +41,23 @@ Looking inside the factory, you can see that sub-folders and some files were cre knitr::include_graphics(here::here("images", "factory_new2.png")) ``` -* The *report_sources* folder will hold your R Markdown scripts, which generate your reports -* The *outputs* folder will hold the report outputs (e.g. HTML, Word, PDF, etc.) -* The *scripts* folder can be used to store other R scripts (e.g. that are sourced by your Rmd scripts) -* The *data* folder can be used to hold your data ("raw" and "clean" subfolders are included) -* A *.here* file, so you can use the **here** package to call files in sub-folders by their relation to this root folder (see [R projects](r_projects.qmd) page for details) -* A *gitignore* file was created in case you link this R project to a Github repository (see [Version control and collaboration with Github](collaboration.qmd)) -* An empty README file, for if you use a Github repository +* The *report_sources* folder will hold your R Markdown scripts, which generate your reports. +* The *outputs* folder will hold the report outputs (e.g. HTML, Word, PDF, etc.). +* The *scripts* folder can be used to store other R scripts (e.g. that are sourced by your Rmd scripts). +* The *data* folder can be used to hold your data ("raw" and "clean" subfolders are included). +* A *.here* file, so you can use the **here** package to call files in sub-folders by their relation to this root folder (see [R projects](r_projects.qmd) page for details). +* A *gitignore* file was created in case you link this R project to a Github repository (see [Version control and collaboration with Github](collaboration.qmd)). +* An empty README file, for if you use a Github repository. -**_CAUTION:_** depending on your computer's setting, files such as ".here" may exist but be invisible. +**_CAUTION:_** depending on your computer's setting, files such as ".here" may exist but be invisible. To see hidden files, please follow the steps [here if you have a Windows computer](https://support.microsoft.com/en-us/windows/show-hidden-files-0320fe58-0117-fd59-6851-9b7f9840fdb2), and [here if you use a Mac](https://www.pcmag.com/how-to/how-to-access-your-macs-hidden-files). Of the default settings, below are several that you might want to adjust within the `new_factory()` command: -* `factory = ` - Provide a name for the factory folder (default is "new_factory") -* `path = ` - Designate a file path for the new factory (default is the working directory) -* `report_sources = ` Provide an alternate name for the subfolder which holds the R Markdown scripts (default is "report_sources") -* `outputs = ` Provide an alternate name for the folder which holds the report outputs (default is "outputs") +* `factory = ` - Provide a name for the factory folder (default is "new_factory"). +* `path = ` - Designate a file path for the new factory (default is the working directory). +* `report_sources = ` Provide an alternate name for the subfolder which holds the R Markdown scripts (default is "report_sources"). +* `outputs = ` Provide an alternate name for the folder which holds the report outputs (default is "outputs"). See `?new_factory` for a complete list of the arguments. @@ -87,8 +85,8 @@ knitr::include_graphics(here::here("images", "factory_overview.png")) From within the factory R project, create a R Markdown report just as you would normally, and save it into the "report_sources" folder. See the [R Markdown](rmarkdown.qmd) page for instructions. For purposes of example, we have added the following to the factory: -* A new R markdown script entitled "daily_sitrep.Rmd", saved within the "report_sources" folder -* Data for the report ("linelist_cleaned.rds"), saved to the "clean" sub-folder within the "data" folder +* A new R markdown script entitled "daily_sitrep.Rmd", saved within the "report_sources" folder. +* Data for the report ("linelist_cleaned.rds"), saved to the "clean" sub-folder within the "data" folder. We can see using `factory_overview()` our R Markdown in the "report_sources" folder and the data file in the "clean" data folder (highlighted): @@ -104,8 +102,8 @@ knitr::include_graphics(here::here("images", "factory_new_rmd.png")) In this simple script, there are commands to: -* Load necessary packages -* Import the linelist data using a filepath from the **here** package (read more in the page on [Import and export](importing.qmd)) +* Load necessary packages. +* Import the linelist data using a filepath from the **here** package (read more in the page on [Import and export](importing.qmd)). ```{r, eval=F} linelist <- import(here("data", "clean", "linelist_cleaned.rds")) @@ -216,11 +214,11 @@ knitr::include_graphics(here::here("images", "factory_overview_all.png")) ``` -* Within "outputs", sub-folders have been created for each Rmd report -* Within those, further sub-folders have been created for each unique compiling - * These are date- and time-stamped ("2021-04-23_T11-07-36" means 23rd April 2021 at 11:07:36) - * You can edit the date/time-stamp format. See `?compile_reports` -* Within each date/time compiled folder, the report output is stored (e.g. HTML, PDF, Word) along with the Rmd script (version control!) and any other exported files (e.g. table.csv, epidemic_curve.png) +* Within "outputs", sub-folders have been created for each Rmd report. +* Within those, further sub-folders have been created for each unique compiling. + * These are date- and time-stamped ("2021-04-23_T11-07-36" means 23rd April 2021 at 11:07:36). + * You can edit the date/time-stamp format. See `?compile_reports`. +* Within each date/time compiled folder, the report output is stored (e.g. HTML, PDF, Word) along with the Rmd script (version control!) and any other exported files (e.g. table.csv, epidemic_curve.png). Here is a view inside one of the date/time-stamped folders, for the "daily_sitrep" report. The file path is highlighted in yellow for emphasis. @@ -257,8 +255,8 @@ We encourage you to utilize the "scripts" folder to store "runfiles" or .R scrip * With **reportfactory**, you can use the function `list_deps()` to list all packages required across all the reports in the entire factory. * There is an accompanying package in development called **rfextras** that offers more helper functions to assist you in building reports, such as: - * `load_scripts()` - sources/loads all .R scripts in a given folder (the "scripts" folder by default) - * `find_latest()` - finds the latest version of a file (e.g. the latest dataset) + * `load_scripts()` - sources/loads all .R scripts in a given folder (the "scripts" folder by default). + * `find_latest()` - finds the latest version of a file (e.g. the latest dataset). @@ -266,7 +264,7 @@ We encourage you to utilize the "scripts" folder to store "runfiles" or .R scrip ## Resources { } -See the **reportfactory** package's [Github page](https://github.com/reconverse/reportfactory) +See the **reportfactory** package's [Github page](https://github.com/reconverse/reportfactory). -See the **rfextras** package's [Github page](https://github.com/reconhub/rfextras) +See the **rfextras** package's [Github page](https://github.com/reconhub/rfextras). diff --git a/new_pages/rmarkdown.qmd b/new_pages/rmarkdown.qmd index 02e81d1d..c8f7cfe5 100644 --- a/new_pages/rmarkdown.qmd +++ b/new_pages/rmarkdown.qmd @@ -39,17 +39,17 @@ In sum, the process that happens *in the background* (you do not need to know al knitr::include_graphics(here::here("images", "markdown/0_rmd.png")) ``` -(source: https://rmarkdown.rstudio.com/authoring_quick_tour.html): +[Source](https://rmarkdown.rstudio.com/authoring_quick_tour.html). **Installation** To create a R Markdown output, you need to have the following installed: -* The **rmarkdown** package (**knitr** will also be installed automatically) -* Pandoc, which should come installed with RStudio. If you are not using RStudio, you can download Pandoc here: http://pandoc.org. -* If you want to generate PDF output (a bit trickier), you will need to install LaTeX. For R Markdown users who have not installed LaTeX before, we recommend that you install TinyTeX (https://yihui.name/tinytex/). You can use the following commands: +* The **rmarkdown** package (**knitr** will also be installed automatically) +* Pandoc, which should come installed with RStudio. If you are not using RStudio, you can download Pandoc [here](http://pandoc.org). +* If you want to generate PDF output (a bit trickier), you will need to install LaTeX. For R Markdown users who have not installed LaTeX before, we recommend that you install [TinyTeX](https://yihui.name/tinytex/). You can use the following commands: -```{r, eval=F} +```{r, eval=F, warning=F, message=F} pacman::p_load(tinytex) # install tinytex package tinytex::install_tinytex() # R command to install TinyTeX software ``` @@ -61,19 +61,19 @@ tinytex::install_tinytex() # R command to install TinyTeX software Install the **rmarkdown** R package. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. -```{r, eval=F} +```{r, eval=F, warning=F, message=F} pacman::p_load(rmarkdown) ``` ### Starting a new Rmd file {.unnumbered} -In RStudio, open a new R markdown file, starting with ‘File’, then ‘New file’ then ‘R markdown…’. +In RStudio, open a new R markdown file, starting with ‘File’, then ‘New file’ then ‘R Markdown…’. ```{r out.width = "50%", fig.align = "center", echo=F} knitr::include_graphics(here::here("images", "markdown/1_gettingstarted.png")) ``` -R Studio will give you some output options to pick from. In the example below we select "HTML" because we want to create an html document. The title and the author names are not important. If the output document type you want is not one of these, don't worry - you can just pick any one and change it in the script later. +R Studio will give you some output options to pick from. In the example below we select "HTML" because we want to create an html document. The title and the author names are not important. If the output document type you want is not one of these, don't worry - you can just pick any one and change it in the script later. If you want to use the current date every time you render the document, such as for an automated report, you can click "Use current date when rendering document", but this can also be updated later. ```{r out.width = "50%", fig.align = "center", echo=F} knitr::include_graphics(here::here("images", "markdown/1_gettingstartedB.png")) @@ -119,7 +119,7 @@ knitr::include_graphics(here::here("images", "markdown/rmarkdown_translation.png ### YAML metadata {.unnumbered} -Referred to as the ‘YAML metadata’ or just ‘YAML’, this is at the top of the R Markdown document. This section of the script will tell your Rmd file what type of output to produce, formatting preferences, and other metadata such as document title, author, and date. There are other uses not mentioned here (but referred to in ‘Producing an output’). Note that indentation matters; tabs are not accepted but spaces are. +Referred to as the ‘YAML metadata’ or just ‘YAML’, this is at the top of the R Markdown document. This section of the script will tell your Rmd file what type of output to produce, formatting preferences, and other metadata such as document title, author, and date. There are other uses not mentioned here (but referred to in [Producing the document](rmarkdown.qmd#producing-the-document). Note that indentation matters; tabs are not accepted but spaces are. This section must begin with a line containing just three dashes `---` and must close with a line containing just three dashes `---`. YAML parameters comes in `key:value` pairs. The placement of colons in YAML is important - the `key:value` pairs are separated by colons (not equals signs!). @@ -140,11 +140,11 @@ In the image above, because we clicked that our default output would be an html This is the narrative of your document, including the titles and headings. It is written in the "markdown" language, which is used across many different software. -Below are the core ways to write this text. See more extensive documentation available on R Markdown "cheatsheet" at the [RStudio website](https://rstudio.com/resources/cheatsheets/). +Below are the core ways to write this text. See more extensive documentation available on R Markdown "cheatsheet" at the [RStudio website](https://posit.co/blog/the-r-markdown-cheat-sheet/). #### New lines {.unnumbered} -Uniquely in R Markdown, to initiate a new line, enter *two spaces** at the end of the previous line and then Enter/Return. +Uniquely in R Markdown, to initiate a new line, enter *two spaces* at the end of the previous line and then Enter/Return. @@ -154,7 +154,7 @@ Surround your normal text with these character to change how it appears in the o * Underscores (`_text_`) or single asterisk (`*text*`) to _italicise_ * Double asterisks (`**text**`) for **bold text** -* Back-ticks (````text````) to display text as code +* Back-ticks (````text````) to display text as code The actual appearance of the font can be set by using specific templates (specified in the YAML metadata; see example tabs). @@ -222,18 +222,18 @@ You can create a new chunk by typing it out yourself, by using the keyboard shor Some notes about the contents of the curly brackets `{ }`: * They start with ‘r’ to indicate that the language name within the chunk is R -* After the r you can optionally write a chunk "name" – these are not necessary but can help you organise your work. Note that if you name your chunks, you should ALWAYS use unique names or else R will complain when you try to render. +* After the r you can optionally write a chunk "name" – these are not necessary but can help you organise your work. Note that if you name your chunks, you should ALWAYS use unique names or else R will complain when you try to render * The curly brackets can include other options too, written as `tag=value`, such as: - * `eval = FALSE` to not run the R code - * `echo = FALSE` to not print the chunk's R source code in the output document - * `warning = FALSE` to not print warnings produced by the R code - * `message = FALSE` to not print any messages produced by the R code + * `eval = FALSE` to not run the R code + * `echo = FALSE` to not print the chunk's R source code in the output document + * `warning = FALSE` to not print warnings produced by the R code + * `message = FALSE` to not print any messages produced by the R code * `include =` either TRUE/FALSE whether to include chunk outputs (e.g. plots) in the document - * `out.width = ` and `out.height =` - provide in style `out.width = "75%"` - * `fig.align = "center"` adjust how a figure is aligned across the page - * `fig.show='hold'` if your chunk prints multiple figures and you want them printed next to each other (pair with `out.width = c("33%", "67%")`. Can also set as `fig.show='asis'` to show them below the code that generates them, `'hide'` to hide, or `'animate'` to concatenate multiple into an animation. -* A chunk header must be written in *one line* -* Try to avoid periods, underscores, and spaces. Use hyphens ( - ) instead if you need a separator. + * `out.width = ` and `out.height =` - provide in style `out.width = "75%"` + * `fig.align = "center"` adjust how a figure is aligned across the page + * `fig.show='hold'` if your chunk prints multiple figures and you want them printed next to each other (pair with `out.width = c("33%", "67%")`. Can also set as `fig.show='asis'` to show them below the code that generates them, `'hide'` to hide, or `'animate'` to concatenate multiple into an animation +* A chunk header must be written in *one line* +* Try to avoid periods, underscores, and spaces. Use hyphens ( - ) instead if you need a separator Read more extensively about the **knitr** options [here](https://yihui.org/knitr/options/). @@ -320,7 +320,7 @@ Cell D |Cell E |Cell F ### Tabbed sections {.unnumbered} -For HTML outputs, you can arrange the sections into "tabs". Simply add `.tabset` in the curly brackets `{ }` that are placed *after a heading*. Any sub-headings beneath that heading (until another heading of the same level) will appear as tabs that the user can click through. Read more [here](https://bookdown.org/yihui/rmarkdown-cookbook/html-tabs.html) +For HTML outputs, you can arrange the sections into "tabs". Simply add `.tabset` in the curly brackets `{ }` that are placed *after a heading*. Any sub-headings beneath that heading (until another heading of the same level) will appear as tabs that the user can click through. Read more [here](https://bookdown.org/yihui/rmarkdown-cookbook/html-tabs.html). @@ -333,19 +333,29 @@ knitr::include_graphics(here::here("images", "markdown/tabbed_view.gif")) You can add an additional option `.tabset-pills` after `.tabset` to give the tabs themselves a "pilled" appearance. Be aware that when viewing the tabbed HTML output, the Ctrl+f search functionality will only search "active" tabs, not hidden tabs. +### **remedy** {.unnumbered} +remedy is an addin for R-Studio which helps with writing R Markdown scripts. It provides a user interface and series of keyboard shortcuts to format your text. +This package is installed directly from GitHub. + +```{r, eval = F} +remotes::install_github("ThinkR-open/remedy") +``` +Once installed, the package does not need to be re-loaded. It will automatically load when you start RStudio. + +For a full list of features and updates, please see [https://thinkr-open.github.io/remedy/](https://thinkr-open.github.io/remedy/) ## File structure {} There are several ways to structure your R Markdown and any associated R scripts. Each has advantages and disadvantages: -* Self-contained R Markdown - everything needed for the report is imported or created within the R Markdown - * Source other files - You can run external R scripts with the `source()` command and use their outputs in the Rmd - * Child scripts - an alternate mechanism for `source()` -* Utilize a "runfile" - Run commands in an R script *prior to* rendering the R Markdown +* Self-contained R Markdown - everything needed for the report is imported or created within the R Markdown. + * Source other files - You can run external R scripts with the `source()` command and use their outputs in the Rmd. + * Child scripts - an alternate mechanism for `source()`. +* Utilize a "runfile" - Run commands in an R script *prior to* rendering the R Markdown. ### Self-contained Rmd {.unnumbered} @@ -356,12 +366,12 @@ Everything you need to run the R markdown is imported or created within the Rmd In this scenario, one logical organization of the R Markdown script might be: -1) Set global **knitr** options -2) Load packages -3) Import data -4) Process data -5) Produce outputs (tables, plots, etc.) -6) Save outputs, if applicable (.csv, .png, etc.) +1) Set global **knitr** options +2) Load packages +3) Import data +4) Process data +5) Produce outputs (tables, plots, etc.) +6) Save outputs, if applicable (.csv, .png, etc.) #### Source other files {.unnumbered} @@ -376,10 +386,41 @@ source("your-script.R", local = knitr::knit_global()) Note that when using `source()` *within* the R Markdown, the external files will still be run *during the course of rendering your Rmd file*. Therefore, each script is run every time you render the report. Thus, having these `source()` commands *within* the R Markdown does not speed up your run time, nor does it greatly assist with de-bugging, as error produced will still be printed when producing the R Markdown. -An alternative is to utilize the `child = ` **knitr** option. EXPLAIN MORE TO DO +An alternative is to utilize the `child = ` **knitr** option. -You must be aware of various R *environments*. Objects created within an environment will not necessarily be available to the environment used by the R Markdown. +#### `child = ` {.unnumbered} + +If you have a very long R markdown document, with several distinct sections, you might want to create several smaller documents (referred to as child documents), and combine them into the finished report. + +This approach can be particularly useful if you want to have the option to include or omit certain sections or analyses when you re-run the R markdown document. For instance, for a daily update on an outbreak you may only want a rapid, streamlined, update, but for a weekly report you may want a more in depth analysis. + +For example, imagine you have a report that creates a summary of an outbreak, and every Sunday it runs additional analysis looking at the demographics of the outbreak. Here we will have our main R markdown file "main_report.Rmd", and the separate analysis in the file "demographic_analysis.Rmd". + +Here we have our document main_report.Rmd which runs the standard analysis, and we have an additional section where we will call the file demographic_analysis.Rmd. This is done by specifying the .Rmd file we want in an R code block. This allows us to use the function `knit_child()` from the **kintr** package to call the .Rmd file and knit it within our primary R markdown file. + +We have additionally included an `if()` and `ifelse()` statement to check if the day of the week (using the function `wday()` from the **lubridate** package) is Sunday, the day of the week we want the additional analysis to be run. + +```{r out.width = "100%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "markdown/11_childdocument_call.png")) +``` + +If the day _is_ Sunday, then it will knit demographic_analysis.Rmd into the main document. +For example, the output when it is **not** Sunday. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "markdown/12_notsundayanalysis.png")) +``` + +and when it **is** Sunday. + +```{r out.width = "50%", fig.align = "center", echo=F} +knitr::include_graphics(here::here("images", "markdown/12_sundayanalysis.png")) +``` + +**Note: Your code chunks cannot share names between the main document and the child documents, or it wil not run.** + +You must be aware of various R *environments*. Objects created within an environment will not necessarily be available to the environment used by the R Markdown. ### Runfile {.unnumbered} @@ -390,8 +431,8 @@ For instance, you can load the packages, load and clean the data, and even creat This approach is helpful for the following reasons: -* More informative error messages - these messages will be generated from the R script, not the R Markdown. R Markdown errors tend to tell you which chunk had a problem, but will not tell you which line. -* If applicable, you can run long processing steps in advance of the `render()` command - they will run only once. +* More informative error messages - these messages will be generated from the R script, not the R Markdown. R Markdown errors tend to tell you which chunk had a problem, but will not tell you which line +* If applicable, you can run long processing steps in advance of the `render()` command - they will run only once In the example below, we have a separate R script in which we pre-process a `data` object into the R Environment and then render the "create_output.Rmd" using `render()`. @@ -409,7 +450,7 @@ rmarkdown::render(input = "create_output.Rmd") # Create Rmd file ### Folder strucutre {.unnumbered} -Workflow also concerns the overall folder structure, such as having an 'output' folder for created documents and figures, and 'data' or 'inputs' folders for cleaned data. We do not go into further detail here, but check out the [Organizing routine reports](reportfactory.qmd) page. +Workflow also concerns the overall folder structure, such as having an 'output' folder for created documents and figures, and 'data' or 'inputs' folders for cleaned data. We do not go into further detail here, but please refer to the [Organizing routine reports](reportfactory.qmd) page. @@ -451,9 +492,9 @@ rmarkdown::render(input = "my_report.Rmd") As with "knit", the default settings will save the Rmd output to the same folder as the Rmd script, with the same file name (aside from the file extension). For instance “my_report.Rmd” when knitted will create “my_report.docx” if you are knitting to a word document. However, by using `render()` you have the option to use different settings. `render()` can accept arguments including: -* `output_format = ` This is the output format to convert to (e.g. `"html_document"`, `"pdf_document"`, `"word_document"`, or `"all"`). You can also specify this in the YAML inside the R Markdown script. -* `output_file = ` This is the name of the output file (and file path). This can be created via R functions like `here()` or `str_glue()` as demonstrated below. -* `output_dir = ` This is an output directory (folder) to save the file. This allows you to chose an alternative other than the directory the Rmd file is saved to. +* `output_format = ` This is the output format to convert to (e.g. `"html_document"`, `"pdf_document"`, `"word_document"`, or `"all"`). You can also specify this in the YAML inside the R Markdown script +* `output_file = ` This is the name of the output file (and file path). This can be created via R functions like `here()` or `str_glue()` as demonstrated below +* `output_dir = ` This is an output directory (folder) to save the file. This allows you to chose an alternative other than the directory the Rmd file is saved to * `output_options = ` You can provide a list of options that will override those in the script YAML (e.g. ) * `output_yaml = ` You can provide path to a .yml file that contains YAML specifications * `params = ` See the section on parameters below @@ -556,10 +597,10 @@ rmarkdown::render( However, typing values into this pop-up window is subject to error and spelling mistakes. You may prefer to add restrictions to the values that can be entered through drop-down menus. You can do this by adding in the YAML several specifications for each `params: ` entry. -* `label: ` is how the title for that particular drop-down menu -* `value: ` is the default (starting) value -* `input: ` set to `select` for drop-down menu -* `choices: ` Give the eligible values in the drop-down menu +* `label: ` is the title for that particular drop-down menu +* `value: ` is the default (starting) value +* `input: ` set to `select` for drop-down menu +* `choices: ` provide the eligible values in the drop-down menu Below, these specifications are written for the `hospital` parameter. @@ -695,8 +736,8 @@ knitr::include_graphics(here::here("images", "markdown/8_ppttemplate.png")) Unfortunately, editing powerpoint files is slightly less flexible: * A first level header (`# Header 1`) will automatically become the title of a new slide, -* A `## Header 2` text will not come up as a subtitle but text within the slide's main textbox (unless you find a way to maniuplate the Master view). -* Outputted plots and tables will automatically go into new slides. You will need to combine them, for instance the the **patchwork** function to combine ggplots, so that they show up on the same page. See this [blog post](https://mattherman.info/blog/ppt-patchwork/) about using the **patchwork** package to put multiple images on one slide. +* A `## Header 2` text will not come up as a subtitle but text within the slide's main textbox (unless you find a way to maniuplate the Master view) +* Outputted plots and tables will automatically go into new slides. You will need to combine them, for instance the the **patchwork** function to combine ggplots, so that they show up on the same page. See this [blog post](https://mattherman.info/blog/ppt-patchwork/) about using the **patchwork** package to put multiple images on one slide See the [**officer** package](https://davidgohel.github.io/officer/) for a tool to work more in-depth with powerpoint presentations. @@ -729,7 +770,7 @@ HTML files do not use templates, but can have the styles configured within the Y * Table of contents: We can add a table of contents with `toc: true` below, and also specify that it remains viewable ("floats") as you scroll, with `toc_float: true`. -* Themes: We can refer to some pre-made themes, which come from a Bootswatch theme library. In the below example we use cerulean. Other options include: journal, flatly, darkly, readable, spacelab, united, cosmo, lumen, paper, sandstone, simplex, and yeti. +* Themes: We can refer to some pre-made themes, which come from a [Bootswatch theme library](https://bootswatch.com/3/). In the below example we use cerulean. Other options include: journal, flatly, darkly, readable, spacelab, united, cosmo, lumen, paper, sandstone, simplex, and yeti. See [here](https://bootswatch.com/3/) for a full list of freely available themes. * Highlight: Configuring this changes the look of highlighted text (e.g. code within chunks that are shown). Supported styles include default, tango, pygments, kate, monochrome, espresso, zenburn, haddock, breezedark, and textmate. @@ -783,9 +824,9 @@ Some common examples of these widgets include: * Plotly (used in this handbook page and in the [Interative plots](interactive_plots.qmd) page) * visNetwork (used in the [Transmission Chains](transmission_chains.qmd) page of this handbook) -* Leaflet (used in the [GIS Basics](gis.qmd) page of this handbook) -* dygraphs (useful for interactively showing time series data) -* DT (`datatable()`) (used to show dynamic tables with filter, sort, etc.) +* Leaflet (used in the [GIS Basics](gis.qmd) page of this handbook) +* dygraphs (useful for interactively showing time series data) +* DT (`datatable()`) (used to show dynamic tables with filter, sort, etc.) The `ggplotly()` function from **plotly** is particularly easy to use. See the [Interactive plots](interactive_plots.qmd) page. @@ -794,9 +835,9 @@ The `ggplotly()` function from **plotly** is particularly easy to use. See the [ Further information can be found via: -* https://bookdown.org/yihui/rmarkdown/ -* https://rmarkdown.rstudio.com/articles_intro.html - -A good explainer of markdown vs knitr vs Rmarkdown is here: https://stackoverflow.com/questions/40563479/relationship-between-r-markdown-knitr-pandoc-and-bookdown +* [R Markdown: The Definitive Guide](https://bookdown.org/yihui/rmarkdown/) +* [Introduction to R Markdown](https://rmarkdown.rstudio.com/articles_intro.html) +A good explainer of Markdown vs knitr vs R Markdown can be found [here](https://stackoverflow.com/questions/40563479/relationship-between-r-markdown-knitr-pandoc-and-bookdown +). diff --git a/new_pages/shiny_basics.qmd b/new_pages/shiny_basics.qmd index 78eb9512..8a9382d2 100644 --- a/new_pages/shiny_basics.qmd +++ b/new_pages/shiny_basics.qmd @@ -56,21 +56,25 @@ In this page, we will use the first approach of having one file called *app.R*. library(shiny) ui <- fluidPage( - - # Application title - titlePanel("My app"), - - # Sidebar with a slider input widget - sidebarLayout( - sidebarPanel( - sliderInput("input_1") - ), - - # Show a plot - mainPanel( - plotOutput("my_plot") - ) - ) + + # Application title + titlePanel("My app"), + + # Sidebar with a slider input widget + sidebarLayout( + sidebarPanel( + sliderInput(inputId = "input_1", + label = "Slider", + min = 1, + max = 50, + value = 1) + ), + + # Show a plot + mainPanel( + plotOutput("my_plot") + ) + ) ) # Define server logic required to draw a histogram @@ -103,11 +107,11 @@ We next need to understand what the `server` and `ui` objects actually _do_. *Pu The UI element of a shiny app is, on a basic level, R code that creates an HTML interface. This means everything that is *displayed* in the UI of an app. This generally includes: -* "Widgets" - dropdown menus, check boxes, sliders, etc that can be interacted with by the user -* Plots, tables, etc - outputs that are generated with R code +* "Widgets" - dropdown menus, check boxes, sliders, etc that can be interacted with by the user. +* Plots, tables, etc - outputs that are generated with R code. * Navigation aspects of an app - tabs, panes, etc. -* Generic text, hyperlinks, etc -* HTML and CSS elements (addressed later) +* Generic text, hyperlinks, etc. +* HTML and CSS elements (addressed later). The most important thing to understand about the UI is that it *receives inputs* from the user and *displays outputs* from the server. There is no *active* code running in the ui *at any time* - all changes seen in the UI are passed through the server (more or less). So we have to make our plots, downloads, etc in the server @@ -120,9 +124,10 @@ This all probably sounds very abstract for now, so we'll have to dive into some Before you begin to build an app, its immensely helpful to know *what* you want to build. Since your UI will be written in code, you can't really visualise what you're building unless you are aiming for something specific. For this reason, it is immensely helpful to look at lots of examples of shiny apps to get an idea of what you can make - even better if you can look at the source code behind these apps! Some great resources for this are: -* The [Rstudio app gallery](https://shiny.rstudio.com/gallery/) +* [Rstudio app gallery](https://shiny.rstudio.com/gallery/) +* [Appsilon R Shiny Demo Gallery](https://www.appsilon.com/shiny-demo-gallery) -Once you get an idea for what is possible, it's also helpful to map out what you want yours to look like - you can do this on paper or in any drawing software (PowerPoint, MS paint, etc.). It's helpful to start simple for your first app! There's also no shame in using code you find online of a nice app as a template for your work - its much easier than building something from scratch! +Once you get an idea for what is possible, it's also helpful to map out what you want yours to look like - you can do this on paper or in any drawing software (PowerPoint, MS paint, etc.). It's helpful to start simple for your first app! There's also no shame in using code you find online of a nice app as a template for your work - its much easier and more efficient than building something from scratch! @@ -130,17 +135,17 @@ Once you get an idea for what is possible, it's also helpful to map out what you When building our app, its easier to work on the UI first so we can see what we're making, and not risk the app failing because of any server errors. As mentioned previously, its often good to use a template when working on the UI. There are a number of standard layouts that can be used with shiny that are available from the base shiny package, but it's worth noting that there are also a number of package extensions such as `shinydashboard`. We'll use an example from base shiny to start with. -A shiny UI is generally defined as a series of nested functions, in the following order +A shiny UI is generally defined as a series of nested functions, in the following order: -1. A function defining the general layout (the most basic is `fluidPage()`, but more are available) +1. A function defining the general layout (the most basic is `fluidPage()`, but more are available). 2. Panels within the layout such as: - - a sidebar (`sidebarPanel()`) - - a "main" panel (`mainPanel()`) - - a tab (`tabPanel()`) - - a generic "column" (`column()`) -3. Widgets and outputs - these can confer inputs to the server (widgets) or outputs from the server (outputs) - - Widgets generally are styled as `xxxInput()` e.g. `selectInput()` - - Outputs are generally styled as `xxxOutput()` e.g. `plotOutput()` + - a sidebar (`sidebarPanel()`). + - a "main" panel (`mainPanel()`). + - a tab (`tabPanel()`). + - a generic "column" (`column()`). +3. Widgets and outputs - these can confer inputs to the server (widgets) or outputs from the server (outputs). + - Widgets generally are styled as `xxxInput()` e.g. `selectInput()`. + - Outputs are generally styled as `xxxOutput()` e.g. `plotOutput()`. It's worth stating again that these can't be visualised easily in an abstract way, so it's best to look at an example! Lets consider making a basic app that visualises our malaria facility count data by district. This data has a lot of differnet parameters, so it would be great if the end user could apply some filters to see the data by age group/district as they see fit! We can use a very simple shiny layout to start - the sidebar layout. This is a layout where widgets are placed in a sidebar on the left, and the plot is placed on the right. @@ -228,7 +233,7 @@ It's also worth noting the *named vector* we used for the age data here. For man There are loads of widgets that you can use to do lots of things with your app. Widgets also allow you to upload files into your app, and download outputs. There are also some excellent shiny extensions that give you access to more widgets than base shiny - the **shinyWidgets** package is a great example of this. To look at some examples you can look at the following links: - [base shiny widget gallery](https://shiny.rstudio.com/gallery/widget-gallery.html) -- [shinyWidgets gallery](https://github.com/dreamRs/shinyWidgets) +- [shinyWidgets gallery](https://dreamrs.github.io/shinyWidgets/) @@ -238,24 +243,25 @@ The next step in our app development is getting the server up and running. To do So given we want to make an app that shows epi curves that change based on user input, we should think about what code we would need to run this in a normal R script. We'll need to: -1. Load our packages -2. Load our data -3. Transform our data -4. Develop a _function_ to visualise our data based on user inputs +1. Load our packages. +2. Load our data. +3. Transform our data. +4. Develop a _function_ to visualise our data based on user inputs. This list is pretty straightforward, and shouldn't be too hard to do. It's now important to think about which parts of this process need to *be done only once* and which parts need to *run in response to user inputs*. This is because shiny apps generally run some code before running, which is only performed once. It will help our app's performance if as much of our code can be moved to this section. For this example, we only need to load our data/packages and do basic transformations once, so we can put that code *outside the server*. This means the only thing we'll need in the server is the code to visualise our data. Lets develop all of these componenets in a script first. However, since we're visualising our data with a function, we can also put the code _for the function_ outside the server so our function is in the environment when the app runs! First lets load our data. Since we're working with a new project, and we want to make it clean, we can create a new directory called data, and add our malaria data in there. We can run this code below in a testing script we will eventually delete when we clean up the structure of our app. -```{r, echo = TRUE} -pacman::p_load("tidyverse", "lubridate") +```{r, echo = TRUE, warning=F} +pacman::p_load( + "tidyverse", + "lubridate" + ) # read data -malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% - as_tibble() - -print(malaria_data) +malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) +head(malaria_data) ``` @@ -267,9 +273,11 @@ It will be easier to work with this data if we use tidy data standards, so we sh malaria_data <- malaria_data %>% select(-newid) %>% - pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported") + pivot_longer(cols = starts_with("malaria_"), + names_to = "age_group", + values_to = "cases_reported") -print(malaria_data) +head(malaria_data) ``` @@ -277,9 +285,9 @@ And with that we've finished preparing our data! This crosses items 1, 2, and 3 When defining our function, it might be hard to think about what parameters we want to include. For functional programming with shiny, every relevent parameter will generally have a widget associated with it, so thinking about this is usually quite easy! For example in our current app, we want to be able to filter by district, and have a widget for this, so we can add a district parameter to reflect this. We *don't* have any app functionality to filter by facility (for now), so we don't need to add this as a parameter. Lets start by making a function with three parameters: -1. The core dataset -2. The district of choice -3. The age group of choice +1. The core dataset. +2. The district of choice. +3. The age group of choice. ```{r} @@ -320,8 +328,13 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") { } - ggplot(data, aes(x = data_date, y = cases_reported)) + - geom_col(width = 1, fill = "darkred") + + ggplot(data, + mapping = aes(x = data_date, + y = cases_reported) + ) + + geom_col(width = 1, + fill = "darkred" + ) + theme_minimal() + labs( x = "date", @@ -345,13 +358,15 @@ Let's test our function! ```{r, echo = TRUE, warning = FALSE} -plot_epicurve(malaria_data, district = "Bolo", agegroup = "malaria_rdt_0-4") +plot_epicurve(malaria_data, + district = "Bolo", + agegroup = "malaria_rdt_0-4") ``` With our function working, we now have to understand how this all is going to fit into our shiny app. We mentioned the concept of _startup code_ before, but lets look at how we can actually incorporate this into the structure of our app. There are two ways we can do this! -1. Put this code in your _app.R_ file at the start of the script (above the UI), or +1. Put this code in your _app.R_ file at the start of the script (above the UI), or, 2. Create a new file in your app's directory called _global.R_, and put the startup code in this file. It's worth noting at this point that it's generally easier, especially with bigger apps, to use the second file structure, as it lets you separate your file structure in a simple way. Lets fully develop a this global.R script now. Here is what it could look like: @@ -369,7 +384,9 @@ malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds" # clean data and pivot longer malaria_data <- malaria_data %>% select(-newid) %>% - pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported") + pivot_longer(cols = starts_with("malaria_"), + names_to = "age_group", + values_to = "cases_reported") # define plotting function @@ -411,9 +428,12 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") { agegroup_title <- stringr::str_glue("{str_remove(agegroup, 'malaria_rdt')} years") } - - ggplot(data, aes(x = data_date, y = cases_reported)) + - geom_col(width = 1, fill = "darkred") + + ggplot(data, + mapping = aes(x = data_date, + y = cases_reported) + ) + + geom_col(width = 1, + fill = "darkred") + theme_minimal() + labs( x = "date", @@ -421,9 +441,7 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") { title = stringr::str_glue("Malaria cases - {plot_title_district}"), subtitle = agegroup_title ) - - - + } @@ -445,7 +463,7 @@ Note that you should *always* specify `local = TRUE` in shiny apps, since it wil ## Developing an app server -Now that we have most of our code, we just have to develop our server. This is the final piece of our app, and is probably the hardest to understand. The server is a large R function, but its helpful to think of it as a series of smaller functions, or tasks that the app can perform. It's important to understand that these functions are not executed in a linear order. There is an order to them, but it's not fully necessary to understand when starting out with shiny. At a very basic level, these tasks or functions will activate when there is a change in user inputs that affects them, *unless the developer has set them up so they behave differently*. Again, this is all quite abstract, but lets first go through the three basic types of shiny _objects_ +Now that we have most of our code, we just have to develop our server. This is the final piece of our app, and is probably the hardest to understand. The server is a large R function, but its helpful to think of it as a series of smaller functions, or tasks that the app can perform. It's important to understand that these functions are not executed in a linear order. There is an order to them, but it's not fully necessary to understand when starting out with shiny. At a very basic level, these tasks or functions will activate when there is a change in user inputs that affects them, *unless the developer has set them up so they behave differently*. Again, this is all quite abstract, but lets first go through the three basic types of shiny _objects_: 1. Reactive sources - this is another term for user inputs. The shiny server has access to the outputs from the UI through the widgets we've programmed. Every time the values for these are changed, this is passed down to the server. @@ -508,10 +526,10 @@ ui <- fluidPage( From this code UI we have: - Two inputs: - - District selector (with an inputId of `select_district`) - - Age group selector (with an inputId of `select_agegroup`) + - District selector (with an inputId of `select_district`). + - Age group selector (with an inputId of `select_agegroup`). - One output: - - The epicurve (with an outputId of `malaria_epicurve`) + - The epicurve (with an outputId of `malaria_epicurve`). As stated previously, these unique names we have assigned to our inputs and outputs are crucial. They *must be unique* and are used to pass information between the ui and server. In our server, we access our inputs via the syntax `input$inputID` and outputs and passed to the ui through the syntax `output$output_name` Lets have a look at an example, because again this is hard to understand otherwise! @@ -520,7 +538,9 @@ As stated previously, these unique names we have assigned to our inputs and outp server <- function(input, output, session) { output$malaria_epicurve <- renderPlot( - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) ) } @@ -533,7 +553,7 @@ The server for a simple app like this is actually quite straightforward! You'll To understand the basics of how the server reacts to user inputs, you should note that the output will know (through the underlying package) when inputs change, and rerun this function to create a plot every time they change. Note that we also use the `renderPlot()` function here - this is one of a family of class-specific functions that pass those objects to a ui output. There are a number of functions that behave similarly, but you need to ensure the function used matches the class of object you're passing to the ui! For example: -- `renderText()` - send text to the ui +- `renderText()` - send text to the ui. - `renderDataTable` - send an interactive table to the ui. Remember that these also need to match the output *function* used in the ui - so `renderPlot()` is paired with `plotOutput()`, and `renderText()` is matched with `textOutput()`. @@ -561,9 +581,9 @@ knitr::include_graphics(here::here("images", "shiny", "listening.png")) At this point we've finally got a running app, but we have very little functionality. We also haven't really scratched the surface of what shiny can do, so there's a lot more to learn about! Lets continue to build our existing app by adding some extra features. Some things that could be nice to add could be: -1. Some explanatory text -2. A download button for our plot - this would provide the user with a high quality version of the image that they're generating in the app -3. A selector for specific facilities +1. Some explanatory text. +2. A download button for our plot - this would provide the user with a high quality version of the image that they're generating in the app. +3. A selector for specific facilities. 4. Another dashboard page - this could show a table of our data. This is a lot to add, but we can use it to learn about a bunch of different shiny featues on the way. There is so much to learn about shiny (it can get *very* advanced, but its hopefully the case that once users have a better idea of how to use it they can become more comfortable using external learning sources as well). @@ -776,7 +796,9 @@ Now that we have our ui ready, we need to add the server component. Downloads ar server <- function(input, output, session) { output$malaria_epicurve <- renderPlot( - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) ) output$download_epicurve <- downloadHandler( @@ -786,7 +808,9 @@ server <- function(input, output, session) { content = function(file) { ggsave(file, - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup), + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup), width = 8, height = 5, dpi = 300) } @@ -802,7 +826,7 @@ Note that the `content` function always takes a `file` argument, which we put wh Reactive conductors are objects that are created in the shiny server in a *reactive* way, but are not outputted - they can just be used by other parts of the server. There are a number of different kinds of *reactive conductors*, but we'll go through the basic two. -1.`reactive()` - this is the most basic reactive conductor - it will react whenever any inputs used inside of it change (so our district/age group widgets) +1.`reactive()` - this is the most basic reactive conductor - it will react whenever any inputs used inside of it change (so our district/age group widgets). 2. `eventReactive()`- this rective conductor works the same as `reactive()`, except that the user can specify which inputs cause it to rerun. This is useful if your reactive conductor takes a long time to process, but this will be explained more later. Lets look at the two examples: @@ -811,7 +835,9 @@ Lets look at the two examples: malaria_plot_r <- reactive({ - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) }) @@ -819,7 +845,9 @@ malaria_plot_r <- reactive({ # only runs when the district selector changes! malaria_plot_er <- eventReactive(input$select_district, { - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) }) @@ -837,7 +865,9 @@ Lets look at how we can integrate this into our server code: server <- function(input, output, session) { malaria_plot <- reactive({ - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) }) @@ -936,8 +966,12 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot", faci - ggplot(data, aes(x = data_date, y = cases_reported)) + - geom_col(width = 1, fill = "darkred") + + ggplot(data, + mapping = aes(x = data_date, + y = cases_reported) + ) + + geom_col(width = 1, + fill = "darkred") + theme_minimal() + labs( x = "date", @@ -955,7 +989,10 @@ Let's test it: ```{r, warning=F, message=F} -plot_epicurve(malaria_data, district = "Spring", agegroup = "malaria_rdt_0-4", facility = "Facility 1") +plot_epicurve(malaria_data, + district = "Spring", + agegroup = "malaria_rdt_0-4", + facility = "Facility 1") ``` @@ -1068,17 +1105,17 @@ Notice how we're now passing variables for our choices instead of hard coding th The functions we need to understand how to do this are known as *observer* functions, and are similar to *reactive* functions in how they behave. They have one key difference though: -- Reactive functions do not directly affect outputs, and produce objects that can be seen in other locations in the server -- Observer functions *can* affect server outputs, but do so via side effects of other functions. (They can also do other things, but this is their main function in practice) +- Reactive functions do not directly affect outputs, and produce objects that can be seen in other locations in the server. +- Observer functions *can* affect server outputs, but do so via side effects of other functions. (They can also do other things, but this is their main function in practice). Similar to reactive functions, there are two flavours of observer functions, and they are divided by the same logic that divides reactive functions: -1. `observe()` - this function runs whenever any inputs used inside of it change -2. `observeEvent()` - this function runs when a *user-specified* input changes +1. `observe()` - this function runs whenever any inputs used inside of it change. +2. `observeEvent()` - this function runs when a *user-specified* input changes. We also need to understand the shiny-provided functions that update widgets. These are fairly straightforward to run - they first take the `session` object from the server function (this doesn't need to be understood for now), and then the `inputId` of the function to be changed. We then pass new versions of all parameters that are already taken by `selectInput()` - these will be automatically updated in the widget. -Lets look at an isolated example of how we could use this in our server. When the user changes the district, we want to filter our tibble of facilities by district, and update the choices to *only reflect those that are available in that district* (and an option for all facilities) +Lets look at an isolated example of how we could use this in our server. When the user changes the district, we want to filter our tibble of facilities by district, and update the choices to *only reflect those that are available in that district* (and an option for all facilities). ```{r, eval = FALSE} @@ -1094,7 +1131,8 @@ observe({ new_choices <- c("All", new_choices) - updateSelectInput(session, inputId = "select_facility", + updateSelectInput(session, + inputId = "select_facility", choices = new_choices) }) @@ -1108,7 +1146,10 @@ And that's it! we can add it into our server, and that behaviour will now work. server <- function(input, output, session) { malaria_plot <- reactive({ - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup, + facility = input$select_facility) }) @@ -1125,7 +1166,8 @@ server <- function(input, output, session) { new_choices <- c("All", new_choices) - updateSelectInput(session, inputId = "select_facility", + updateSelectInput(session, + inputId = "select_facility", choices = new_choices) }) @@ -1279,7 +1321,7 @@ ui <- fluidPage( ``` -Now our app is arranged into tabs! Lets make the necessary edits to the server as well. Since we dont need to manipulate our dataset at all before we render it this is actually very simple - we just render the malaria_data dataset via DT::renderDT() to the ui! +Now our app is arranged into tabs! Lets make the necessary edits to the server as well. Since we dont need to manipulate our dataset at all before we render it this is actually very simple - we just render the malaria_data dataset via `DT::renderDT()` to the ui! ```{r, eval = FALSE} @@ -1348,7 +1390,7 @@ knitr::include_graphics(here::here("images", "shiny", "app_table_view.gif")) Now that you've developed your app, you probably want to share it with others - this is the main advantage of shiny after all! We can do this by sharing the code directly, or we could publish on a server. If we share the code, others will be able to see what you've done and build on it, but this will negate one of the main advantages of shiny - *it can eliminate the need for end-users to maintain an R installation*. For this reason, if you're sharing your app with users who are not comfortable with R, it is much easier to share an app that has been published on a server. -If you'd rather share the code, you could make a .zip file of the app, or better yet, *publish your app on github and add collaborators.* You can refer to the section on github for further information here. +If you'd rather share the code, you could make a .zip file of the app, or better yet, *publish your app on github and add collaborators.* You can refer to the section on github for further information [here](collaboration.qmd). However, if we're publishing the app online, we need to do a little more work. Ultimately, we want your app to be able to be accessed via a web URL so others can get quick and easy access to it. Unfortunately, to publish you app on a server, you need to have access to a server to publish it on! There are a number of hosting options when it comes to this: @@ -1356,23 +1398,14 @@ However, if we're publishing the app online, we need to do a little more work. U - _RStudio Connect_: this is a far more powerful version of an R server, that can perform many operations, including publishing shiny apps. It is however, harder to use, and less recommended for first-time users. -For the purposes of this document, we will use _shinyapps.io_, since it is easier for first time users. You can make a free account here to start - there are also different price plans for server licesnses if needed. The more users you expect to have, the more expensive your price plan may have to be, so keep this under consideration. If you're looking to create something for a small set of individuals to use, a free license may be perfectly suitable, but a public facing app may need more licenses. +For the purposes of this document, we will use _shinyapps.io_, since it is easier for first time users. You can make a free account [here](https://www.shinyapps.io/) to start - there are also different price plans for server licesnses if needed. The more users you expect to have, the more expensive your price plan may have to be, so keep this under consideration. If you're looking to create something for a small set of individuals to use, a free license may be perfectly suitable, but a public facing app may need more licenses. -First we should make sure our app is suitable for publishing on a server. In your app, you should restart your R session, and ensure that it runs without running any extra code. This is important, as an app that requires package loading, or data reading not defined in your app code won't run on a server. Also note that you can't have any *explicit* file paths in your app - these will be invalid in the server setting - using the `here` package solves this issue very well. Finally, if you're reading data from a source that requires user-authentication, such as your organisation's servers, this will not generally work on a server. You will need to liase with your IT department to figure out how to whitelist the shiny server here. - -*signing up for account* +First we should make sure our app is suitable for publishing on a server. In your app, you should restart your R session, and ensure that it runs without running any extra code. This is important, as an app that requires package loading, or data reading not defined in your app code won't run on a server. Also note that you can't have any *explicit* file paths in your app - these will be invalid in the server setting - using the `here` package solves this issue very well. Finally, if you're reading data from a source that requires user-authentication, such as your organisation's servers, this will not generally work on a server. You will need to liase with your IT department to figure out how to whitelist the shiny server [here](https://docs.posit.co/shiny-server/). Once you have your account, you can navigate to the tokens page under _Accounts_. Here you will want to add a new token - this will be used to deploy your app. From here, you should note that the url of your account will reflect the name of your app - so if your app is called _my_app_, the url will be appended as _xxx.io/my_app/_. Choose your app name wisely! Now that you are all ready, click deploy - if successful this will run your app on the web url you chose! -*something on making apps in documents?* - -## Further reading - -So far, we've covered a lot of aspects of shiny, and have barely scratched the surface of what is on offer for shiny. While this guide serves as an introduction, there is loads more to learn to fully understand shiny. You should start making apps and gradually add more and more functionality - - ## Recommended extension packages The following represents a selection of high quality shiny extensions that can help you get a lot more out of shiny. In no particular order: @@ -1397,5 +1430,9 @@ There are also a number of packages that can be used to create interactive outpu ## Recommended resources +So far, we've covered a lot of aspects of shiny, and have barely scratched the surface of what is on offer for shiny. While this guide serves as an introduction, there is loads more to learn to fully understand shiny. You should start making apps and gradually add more and more functionality. +[Welcome to Shiny (Tutorial)](https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html) +[Introducing Shiny (Tutorial)](http://wch.github.io/shiny/tutorial/) +[A Gentle Introduction to Shiny (Bookdown chapter)](https://bookdown.org/pdr_higgins/rmrwr/a-gentle-introduction-to-shiny.html) diff --git a/new_pages/standardization.qmd b/new_pages/standardization.qmd index 69149615..67af41ec 100644 --- a/new_pages/standardization.qmd +++ b/new_pages/standardization.qmd @@ -1,10 +1,9 @@ # Standardised rates { } -This page will show you two ways to standardize an outcome, such as hospitalizations or mortality, by characteristics such as age and sex. +This page will show you how to standardize an outcome, such as hospitalizations or mortality, by characteristics such as age and sex. -* Using **dsr** package -* Using **PHEindicatormethods** package +We focus on using the **PHEindicatormethods** package We begin by extensively demonstrating the processes of data preparation/cleaning/joining, as this is common when combining population data from multiple countries, standard population data, deaths, etc. @@ -21,8 +20,8 @@ Let's say we would like to the standardize mortality rate by age and sex for cou To show how standardization is done, we will use fictitious population counts and death counts from country A and country B, by age (in 5 year categories) and sex (female, male). To make the datasets ready for use, we will perform the following preparation steps: -1. Load packages -2. Load datasets +1. Load packages +2. Import datasets 3. Join the population and death data from the two countries 4. Pivot longer so there is one row per age-sex stratum 5. Clean the reference population (world standard population) and join it to the country data @@ -41,38 +40,11 @@ pacman::p_load( rio, # import/export data here, # locate files stringr, # cleaning characters and strings - frailtypack, # needed for dsr, for frailty models - dsr, # standardise rates PHEindicatormethods, # alternative for rate standardisation - tidyverse) # data management and visualization + tidyverse # data management and visualization +) ``` - -**_CAUTION:_** If you have a newer version of R, the **dsr** package cannot be directly downloaded from CRAN. However, it is still available from the CRAN archive. You can install and use this one. - -For non-Mac users: - -```{r, eval=F} -packageurl <- "https://cran.r-project.org/src/contrib/Archive/dsr/dsr_0.2.2.tar.gz" -install.packages(packageurl, repos=NULL, type="source") -``` - -```{r, eval=FALSE} -# Other solution that may work -require(devtools) -devtools::install_version("dsr", version="0.2.2", repos="http:/cran.us.r.project.org") -``` - -For Mac users: - -```{r, eval=FALSE} -require(devtools) -devtools::install_version("dsr", version="0.2.2", repos="https://mac.R-project.org") -``` - - - - ### Load population data {.unnumbered} See the [Download handbook and data](data_used.qmd) page for instructions on how to download all the example data in the handbook. You can import the Standardisation page data directly into R from our Github repository by running the following `import()` commands: @@ -178,22 +150,24 @@ rio::export(B_deaths, here::here("data", "standardization", "deaths_countryB.csv We need to join and transform these data in the following ways: -* Combine country populations into one dataset and pivot "long" so that each age-sex stratum is one row -* Combine country death counts into one dataset and pivot "long" so each age-sex stratum is one row -* Join the deaths to the populations +* Combine country populations into one dataset and pivot "long" so that each age-sex stratum is one row. +* Combine country death counts into one dataset and pivot "long" so each age-sex stratum is one row. +* Join the deaths to the populations. First, we combine the country populations datasets, pivot longer, and do minor cleaning. See the page on [Pivoting data](pivoting.qmd) for more detail. ```{r} pop_countries <- A_demo %>% # begin with country A dataset - bind_rows(B_demo) %>% # bind rows, because cols are identically named + mutate(Country = "A") %>% # add in a country identifier + bind_rows(B_demo %>% # bind rows, because cols are identically named + mutate(Country = "B")) %>% # add in country identifier pivot_longer( # pivot longer cols = c(m, f), # columns to combine into one names_to = "Sex", # name for new column containing the category ("m" or "f") values_to = "Population") %>% # name for new column containing the numeric values pivoted mutate(Sex = recode(Sex, # re-code values for clarity - "m" = "Male", - "f" = "Female")) + "m" = "Male", + "f" = "Female")) ``` The combined population data now look like this (click through to see countries A and B): @@ -260,7 +234,7 @@ DT::datatable(country_data, rownames = FALSE, options = list(pageLength = 5, scr ### Load reference population {.unnumbered} -Lastly, for the direct standardisation, we import the reference population (world "standard population" by sex) +Lastly, for the direct standardisation, we import the reference population (world "standard population" by sex): ```{r, echo=F} # Reference population @@ -286,8 +260,6 @@ The age category values in the `country_data` and `standard_pop_data` data frame Currently, the values of the column `age_cat5` from the `standard_pop_data` data frame contain the word "years" and "plus", while those of the `country_data` data frame do not. We will have to make the age category values match. We use `str_replace_all()` from the **stringr** package, as described in the page on [Characters and strings](characters_strings.qmd), to replace these patterns with no space `""`. -Furthermore, the package **dsr** expects that in the standard population, the column containing counts will be called `"pop"`. So we rename that column accordingly. - ```{r} # Remove specific string from column values standard_pop_clean <- standard_pop_data %>% @@ -296,7 +268,7 @@ standard_pop_clean <- standard_pop_data %>% age_cat5 = str_replace_all(age_cat5, "plus", ""), # remove "plus" age_cat5 = str_replace_all(age_cat5, " ", "")) %>% # remove " " space - rename(pop = WorldStandardPopulation) # change col name to "pop", as this is expected by dsr package + rename(pop = WorldStandardPopulation) ``` **_CAUTION:_** If you try to use `str_replace_all()` to remove a plus *symbol*, it won't work because it is a special symbol. "Escape" the specialnes by putting two back slashes in front, as in `str_replace_call(column, "\\+", "")`. @@ -306,7 +278,9 @@ standard_pop_clean <- standard_pop_data %>% Finally, the package **PHEindicatormethods**, detailed [below](#standard_phe), expects the standard populations joined to the country event and population counts. So, we will create a dataset `all_data` for that purpose. ```{r} -all_data <- left_join(country_data, standard_pop_clean, by=c("age_cat5", "Sex")) +all_data <- left_join(country_data, + standard_pop_clean, + by = c("age_cat5", "Sex")) ``` This complete dataset looks like this: @@ -315,120 +289,14 @@ This complete dataset looks like this: DT::datatable(all_data, rownames = FALSE, options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) ``` - - - -## **dsr** package { } - -Below we demonstrate calculating and comparing directly standardized rates using the **dsr** package. The **dsr** package allows you to calculate and compare directly standardized rates (no indirectly standardized rates!). - -In the data Preparation section, we made separate datasets for country counts and standard population: - -1) the `country_data` object, which is a population table with the number of population and number of deaths per stratum per country -2) the `standard_pop_clean` object, containing the number of population per stratum for our reference population, the World Standard Population - -We will use these separate datasets for the **dsr** approach. - - - -### Standardized rates {.unnumbered} - -Below, we calculate rates per country directly standardized for age and sex. We use the `dsr()` function. - -Of note - `dsr()` expects one data frame for the country populations and event counts (deaths), *and a **separate** data frame with the reference population*. It also expects that in this reference population dataset the unit-time column name is "pop" (we assured this in the data Preparation section). - -There are many arguments, as annotated in the code below. Notably, `event = ` is set to the column `Deaths`, and the `fu = ` ("follow-up") is set to the `Population` column. We set the subgroups of comparison as the column `Country` and we standardize based on `age_cat5` and `Sex`. These last two columns are not assigned a particular named argument. See `?dsr` for details. - -```{r, warning=F, message=F} -# Calculate rates per country directly standardized for age and sex -mortality_rate <- dsr::dsr( - data = country_data, # specify object containing number of deaths per stratum - event = Deaths, # column containing number of deaths per stratum - fu = Population, # column containing number of population per stratum - subgroup = Country, # units we would like to compare - age_cat5, # other columns - rates will be standardized by these - Sex, - refdata = standard_pop_clean, # reference population data frame, with column called pop - method = "gamma", # method to calculate 95% CI - sig = 0.95, # significance level - mp = 100000, # we want rates per 100.000 population - decimals = 2) # number of decimals) - - -# Print output as nice-looking HTML table -knitr::kable(mortality_rate) # show mortality rate before and after direct standardization -``` - -Above, we see that while country A had a lower crude mortality rate than country B, it has a higher standardized rate after direct age and sex standardization. - - - - - -### Standardized rate ratios {.unnumbered} - -```{r,warning=F, message=F} -# Calculate RR -mortality_rr <- dsr::dsrr( - data = country_data, # specify object containing number of deaths per stratum - event = Deaths, # column containing number of deaths per stratum - fu = Population, # column containing number of population per stratum - subgroup = Country, # units we would like to compare - age_cat5, - Sex, # characteristics to which we would like to standardize - refdata = standard_pop_clean, # reference population, with numbers in column called pop - refgroup = "B", # reference for comparison - estimate = "ratio", # type of estimate - sig = 0.95, # significance level - mp = 100000, # we want rates per 100.000 population - decimals = 2) # number of decimals - -# Print table -knitr::kable(mortality_rr) -``` - -The standardized mortality rate is 1.22 times higher in country A compared to country B (95% CI 1.17-1.27). - - -### Standardized rate difference {.unnumbered} - -```{r, warning=F, message=F} -# Calculate RD -mortality_rd <- dsr::dsrr( - data = country_data, # specify object containing number of deaths per stratum - event = Deaths, # column containing number of deaths per stratum - fu = Population, # column containing number of population per stratum - subgroup = Country, # units we would like to compare - age_cat5, # characteristics to which we would like to standardize - Sex, - refdata = standard_pop_clean, # reference population, with numbers in column called pop - refgroup = "B", # reference for comparison - estimate = "difference", # type of estimate - sig = 0.95, # significance level - mp = 100000, # we want rates per 100.000 population - decimals = 2) # number of decimals - -# Print table -knitr::kable(mortality_rd) -``` - -Country A has 4.24 additional deaths per 100.000 population (95% CI 3.24-5.24) compared to country A. - - - - - - - ## **PHEindicatormethods** package {#standard_phe } -Another way of calculating standardized rates is with the **PHEindicatormethods** package. This package allows you to calculate directly as well as indirectly standardized rates. We will show both. +One way of calculating standardized rates is with the **PHEindicatormethods** package. This package allows you to calculate directly as well as indirectly standardized rates. We will show both. This section will use the `all_data` data frame created at the end of the Preparation section. This data frame includes the country populations, death events, and the world standard reference population. You can view it [here](#standard_all). - ### Directly standardized rates {.unnumbered} @@ -484,10 +352,8 @@ knitr::kable(mortality_is_rate_phe_A) ## Resources { } -If you would like to see another reproducible example using **dsr** please see [this vignette]( https://mran.microsoft.com/snapshot/2020-02-12/web/packages/dsr/vignettes/dsr.html) - -For another example using **PHEindicatormethods**, please go to [this website](https://mran.microsoft.com/snapshot/2018-10-22/web/packages/PHEindicatormethods/vignettes/IntroductiontoPHEindicatormethods.html) +For another example using **PHEindicatormethods**, please go to [this website](https://cran.r-project.org/web/packages/PHEindicatormethods/vignettes/Introduction_to_PHEindicatormethods.html). -See the **PHEindicatormethods** [reference pdf file](https://cran.r-project.org/web/packages/PHEindicatormethods/PHEindicatormethods.pdf) +See the **PHEindicatormethods** [reference pdf file](https://cran.r-project.org/web/packages/PHEindicatormethods/PHEindicatormethods.pdf). diff --git a/new_pages/stat_tests.qmd b/new_pages/stat_tests.qmd index 49b5b471..2161387e 100644 --- a/new_pages/stat_tests.qmd +++ b/new_pages/stat_tests.qmd @@ -3,7 +3,7 @@ This page demonstrates how to conduct simple statistical tests using **base** R, **rstatix**, and **gtsummary**. -* T-test +* T-test * Shapiro-Wilk test * Wilcoxon rank sum test * Kruskal-Wallis test @@ -14,9 +14,9 @@ This page demonstrates how to conduct simple statistical tests using **base** R, Each of the above packages bring certain advantages and disadvantages: -* Use **base** R functions to print a statistical outputs to the R Console -* Use **rstatix** functions to return results in a data frame, or if you want tests to run by group -* Use **gtsummary** if you want to quickly print publication-ready tables +* Use **base** R functions to print a statistical outputs to the R Console. +* Use **rstatix** functions to return results in a data frame, or if you want tests to run by group. +* Use **gtsummary** if you want to quickly print publication-ready tables. @@ -48,7 +48,7 @@ pacman::p_load( We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -241,7 +241,7 @@ Many many more functions and statistical tests can be run with **rstatix** funct -## `gtsummary` package {#stats_gt} +## **`gtsummary`** package {#stats_gt} Use **gtsummary** if you are looking to add the results of a statistical test to a pretty table that was created with this package (as described in the **gtsummary** section of the [Descriptive tables](tables_descriptive.qmd#tbl_gt) page). @@ -315,8 +315,6 @@ linelist %>% ``` - - @@ -498,6 +496,15 @@ rplot(correlation_tab) Much of the information in this page is adapted from these resources and vignettes online: [gtsummary](http://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html) + [dplyr](https://dplyr.tidyverse.org/articles/grouping.html) + [corrr](https://corrr.tidymodels.org/articles/using-corrr.html) + [sthda correlation](http://www.sthda.com/english/wiki/correlation-test-between-two-variables-in-r) + +Resources for statistical theory + +[Causal Inference: What If](https://www.hsph.harvard.edu/miguel-hernan/wp-content/uploads/sites/1268/2024/04/hernanrobins_WhatIf_26apr24.pdf) + +[Discovering statistics](https://discoveringstatistics.com/) diff --git a/new_pages/stat_tests.rmarkdown b/new_pages/stat_tests.rmarkdown new file mode 100644 index 00000000..f5e81802 --- /dev/null +++ b/new_pages/stat_tests.rmarkdown @@ -0,0 +1,553 @@ +# Simple statistical tests { } + + +This page demonstrates how to conduct simple statistical tests using **base** R, **rstatix**, and **gtsummary**. + +* T-test +* Shapiro-Wilk test +* Wilcoxon rank sum test +* Kruskal-Wallis test +* Chi-squared test +* Correlations between numeric variables + +...many other tests can be performed, but we showcase just these common ones and link to further documentation. + +Each of the above packages bring certain advantages and disadvantages: + +* Use **base** R functions to print a statistical outputs to the R Console. +* Use **rstatix** functions to return results in a data frame, or if you want tests to run by group. +* Use **gtsummary** if you want to quickly print publication-ready tables. + + + + +## Preparation { } + + +### Load packages {.unnumbered} + +This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. + + + +```{r} +pacman::p_load( + rio, # File import + here, # File locator + skimr, # get overview of data + tidyverse, # data management + ggplot2 graphics, + gtsummary, # summary statistics and tests + rstatix, # statistics + corrr, # correlation analayis for numeric variables + janitor, # adding totals and percents to tables + flextable # converting tables to HTML + ) +``` + + +### Import data {.unnumbered} + +We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). + + + +```{r, echo=F} +# import the linelist into R +linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) +``` + +```{r, eval=F} +# import the linelist +linelist <- import("linelist_cleaned.rds") +``` + + +The first 50 rows of the linelist are displayed below. + + +```{r, message=FALSE, echo=F} +# display the linelist data as a table +DT::datatable(head(linelist, 50), rownames = FALSE, filter="top", options = list(pageLength = 5, scrollX=T), class = 'white-space: nowrap' ) +``` + + + + + + +## **base** R {} + +You can use **base** R functions to conduct statistical tests. The commands are relatively simple and results will print to the R Console for simple viewing. However, the outputs are usually lists and so are harder to manipulate if you want to use the results in subsequent operations. + +### T-tests {.unnumbered} + +A [t-test](https://en.wikipedia.org/wiki/Student%27s_t-test), also called "Student's t-Test", is typically used to determine if there is a significant difference between the means of some numeric variable between two groups. Here we'll show the syntax to do this test depending on whether the columns are in the same data frame. + +**Syntax 1:** This is the syntax when your numeric and categorical columns are in the same data frame. Provide the numeric column on the left side of the equation and the categorical column on the right side. Specify the dataset to `data = `. Optionally, set `paired = TRUE`, and `conf.level = ` (0.95 default), and `alternative = ` (either "two.sided", "less", or "greater"). Enter `?t.test` for more details. + + +```{r} +## compare mean age by outcome group with a t-test +t.test(age_years ~ gender, data = linelist) +``` + + +**Syntax 2:** You can compare two separate numeric vectors using this alternative syntax. For example, if the two columns are in different data sets. + + +```{r, eval=F} +t.test(df1$age_years, df2$age_years) +``` + + +You can also use a t-test to determine whether a sample mean is significantly different from some specific value. Here we conduct a one-sample t-test with the known/hypothesized population mean as `mu = `: + + +```{r, eval=F} +t.test(linelist$age_years, mu = 45) +``` + + +### Shapiro-Wilk test {.unnumbered} + +The [Shapiro-Wilk test](https://en.wikipedia.org/wiki/Shapiro%E2%80%93Wilk_test) can be used to determine whether a sample came from a normally-distributed population (an assumption of many other tests and analysis, such as the t-test). However, this can only be used on a sample between 3 and 5000 observations. For larger samples a [quantile-quantile plot](https://ggplot2.tidyverse.org/reference/geom_qq.html) may be helpful. + + + +```{r, eval=F} +shapiro.test(linelist$age_years) +``` + + +### Wilcoxon rank sum test {.unnumbered} + +The Wilcoxon rank sum test, also called the [Mann–Whitney U test](https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test), is often used to help determine if two numeric samples are from the same distribution when their populations are not normally distributed or have unequal variance. + + +```{r wilcox_base} + +## compare age distribution by outcome group with a wilcox test +wilcox.test(age_years ~ outcome, data = linelist) + +``` + + + +### Kruskal-Wallis test {.unnumbered} + + +The [Kruskal-Wallis test](https://en.wikipedia.org/wiki/Kruskal%E2%80%93Wallis_one-way_analysis_of_variance) is an extension of the Wilcoxon rank sum test that can be used to test for differences in the distribution of more than two samples. When only two samples are used it gives identical results to the Wilcoxon rank sum test. + + +```{r } + +## compare age distribution by outcome group with a kruskal-wallis test +kruskal.test(age_years ~ outcome, linelist) + +``` + + +### Chi-squared test {.unnumbered} + +[Pearson's Chi-squared test](https://en.wikipedia.org/wiki/Chi-squared_test) is used in testing for significant differences between categorical croups. + + +```{r} + +## compare the proportions in each group with a chi-squared test +chisq.test(linelist$gender, linelist$outcome) + +``` + + + + +## **rstatix** package {} + +The **rstatix** package offers the ability to run statistical tests and retrieve results in a "pipe-friendly" framework. The results are automatically in a data frame so that you can perform subsequent operations on the results. It is also easy to group the data being passed into the functions, so that the statistics are run for each group. + + +### Summary statistics {.unnumbered} + +The function `get_summary_stats()` is a quick way to return summary statistics. Simply pipe your dataset to this function and provide the columns to analyse. If no columns are specified, the statistics are calculated for all columns. + +By default, a full range of summary statistics are returned: n, max, min, median, 25%ile, 75%ile, IQR, median absolute deviation (mad), mean, standard deviation, standard error, and a confidence interval of the mean. + + + +```{r} +linelist %>% + rstatix::get_summary_stats(age, temp) +``` + + +You can specify a subset of summary statistics to return by providing one of the following values to `type = `: "full", "common", "robust", "five_number", "mean_sd", "mean_se", "mean_ci", "median_iqr", "median_mad", "quantile", "mean", "median", "min", "max". + +It can be used with grouped data as well, such that a row is returned for each grouping-variable: + + +```{r} +linelist %>% + group_by(hospital) %>% + rstatix::get_summary_stats(age, temp, type = "common") +``` + + +You can also use **rstatix** to conduct statistical tests: + +### T-test {.unnumbered} + +Use a formula syntax to specify the numeric and categorical columns: + + +```{r} +linelist %>% + t_test(age_years ~ gender) +``` + + +Or use `~ 1` and specify `mu = ` for a one-sample T-test. This can also be done by group. + + +```{r} +linelist %>% + t_test(age_years ~ 1, mu = 30) +``` + + +If applicable, the statistical tests can be done by group, as shown below: + + +```{r} +linelist %>% + group_by(gender) %>% + t_test(age_years ~ 1, mu = 18) +``` + + +### Shapiro-Wilk test {.unnumbered} + +As stated above, sample size must be between 3 and 5000. + + +```{r} +linelist %>% + head(500) %>% # first 500 rows of case linelist, for example only + shapiro_test(age_years) +``` + + +### Wilcoxon rank sum test {.unnumbered} + + +```{r} +linelist %>% + wilcox_test(age_years ~ gender) +``` + + + +### Kruskal-Wallis test {.unnumbered} + +Also known as the Mann-Whitney U test. + + +```{r} +linelist %>% + kruskal_test(age_years ~ outcome) +``` + + + +### Chi-squared test {.unnumbered} + +The chi-square test function accepts a table, so first we create a cross-tabulation. There are many ways to create a cross-tabulation (see [Descriptive tables](tables_descriptive.qmd)) but here we use `tabyl()` from **janitor** and remove the left-most column of value labels before passing to `chisq_test()`. + + +```{r} +linelist %>% + tabyl(gender, outcome) %>% + select(-1) %>% + chisq_test() + +``` + + +Many many more functions and statistical tests can be run with **rstatix** functions. See the documentation for **rstatix** [online here](https://github.com/kassambara/rstatix) or by entering ?rstatix. + + + + + +## **`gtsummary`** package {#stats_gt} + +Use **gtsummary** if you are looking to add the results of a statistical test to a pretty table that was created with this package (as described in the **gtsummary** section of the [Descriptive tables](tables_descriptive.qmd#tbl_gt) page). + +Performing statistical tests of comparison with `tbl_summary` is done by adding the +`add_p` function to a table and specifying which test to use. It is possible to get p-values corrected for multiple testing by using the +`add_q` function. Run `?tbl_summary` for details. + +### Chi-squared test {.unnumbered} + +Compare the proportions of a categorical variable in two groups. The default statistical test for `add_p()` when applied to a categorical variable is to perform a chi-squared test of independence with continuity correction, but if any expected call count is below 5 then a Fisher's exact test is used. + + +```{r chi_gt} +linelist %>% + select(gender, outcome) %>% # keep variables of interest + tbl_summary(by = outcome) %>% # produce summary table and specify grouping variable + add_p() # specify what test to perform +``` + + + +### T-tests {.unnumbered} + +Compare the difference in means for a continuous variable in two groups. +For example, compare the mean age by patient outcome. + + +```{r ttest_gt} + +linelist %>% + select(age_years, outcome) %>% # keep variables of interest + tbl_summary( # produce summary table + statistic = age_years ~ "{mean} ({sd})", # specify what statistics to show + by = outcome) %>% # specify the grouping variable + add_p(age_years ~ "t.test") # specify what tests to perform + + +``` + + +### Wilcoxon rank sum test{.unnumbered} + +Compare the distribution of a continuous variable in two groups. The default +is to use the Wilcoxon rank sum test and the median (IQR) when comparing two +groups. However for non-normally distributed data or comparing multiple groups, +the Kruskal-wallis test is more appropriate. + + +```{r wilcox_gt} + +linelist %>% + select(age_years, outcome) %>% # keep variables of interest + tbl_summary( # produce summary table + statistic = age_years ~ "{median} ({p25}, {p75})", # specify what statistic to show (this is default so could remove) + by = outcome) %>% # specify the grouping variable + add_p(age_years ~ "wilcox.test") # specify what test to perform (default so could leave brackets empty) + + +``` + + +### Kruskal-wallis test {.unnumbered} + +Compare the distribution of a continuous variable in two or more groups, +regardless of whether the data is normally distributed. + + +```{r kruskal_gt} + +linelist %>% + select(age_years, outcome) %>% # keep variables of interest + tbl_summary( # produce summary table + statistic = age_years ~ "{median} ({p25}, {p75})", # specify what statistic to show (default, so could remove) + by = outcome) %>% # specify the grouping variable + add_p(age_years ~ "kruskal.test") # specify what test to perform + + +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Correlations + +Correlation between numeric variables can be investigated using the **tidyverse** +**corrr** package. It allows you to compute correlations using Pearson, Kendall +tau or Spearman rho. The package creates a table and also has a function to +automatically plot the values. + + +```{r, warning=F, message=F} + +correlation_tab <- linelist %>% + select(generation, age, ct_blood, days_onset_hosp, wt_kg, ht_cm) %>% # keep numeric variables of interest + correlate() # create correlation table (using default pearson) + +correlation_tab # print + +## remove duplicate entries (the table above is mirrored) +correlation_tab <- correlation_tab %>% + shave() + +## view correlation table +correlation_tab + +## plot correlations +rplot(correlation_tab) +``` + + + + + +## Resources { } + +Much of the information in this page is adapted from these resources and vignettes online: + +[gtsummary](http://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html) + +[dplyr](https://dplyr.tidyverse.org/articles/grouping.html) + +[corrr](https://corrr.tidymodels.org/articles/using-corrr.html) + +[sthda correlation](http://www.sthda.com/english/wiki/correlation-test-between-two-variables-in-r) + diff --git a/new_pages/stat_tests_files/execute-results/html.json b/new_pages/stat_tests_files/execute-results/html.json new file mode 100644 index 00000000..e4a8efc6 --- /dev/null +++ b/new_pages/stat_tests_files/execute-results/html.json @@ -0,0 +1,21 @@ +{ + "hash": "813fb5d7f04039e5a48c9ae706378a8d", + "result": { + "engine": "knitr", + "markdown": "# Simple statistical tests { }\n\n\nThis page demonstrates how to conduct simple statistical tests using **base** R, **rstatix**, and **gtsummary**. \n\n* T-test \n* Shapiro-Wilk test \n* Wilcoxon rank sum test \n* Kruskal-Wallis test \n* Chi-squared test \n* Correlations between numeric variables \n\n...many other tests can be performed, but we showcase just these common ones and link to further documentation. \n\nEach of the above packages bring certain advantages and disadvantages: \n\n* Use **base** R functions to print a statistical outputs to the R Console. \n* Use **rstatix** functions to return results in a data frame, or if you want tests to run by group. \n* Use **gtsummary** if you want to quickly print publication-ready tables. \n\n\n\n\n## Preparation { }\n\n\n### Load packages {.unnumbered}\n\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npacman::p_load(\n rio, # File import\n here, # File locator\n skimr, # get overview of data\n tidyverse, # data management + ggplot2 graphics, \n gtsummary, # summary statistics and tests\n rstatix, # statistics\n corrr, # correlation analayis for numeric variables\n janitor, # adding totals and percents to tables\n flextable # converting tables to HTML\n )\n```\n:::\n\n\n### Import data {.unnumbered}\n\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the \"clean\" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). \n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: The `trust` argument of `import()` should be explicit for serialization formats\nas of rio 1.0.3.\nℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\nℹ The deprecated feature was likely used in the rio package.\n Please report the issue at .\n```\n\n\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n```\n:::\n\n\nThe first 50 rows of the linelist are displayed below.\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n\n\n\n\n## **base** R {}\n\nYou can use **base** R functions to conduct statistical tests. The commands are relatively simple and results will print to the R Console for simple viewing. However, the outputs are usually lists and so are harder to manipulate if you want to use the results in subsequent operations. \n\n### T-tests {.unnumbered} \n\nA [t-test](https://en.wikipedia.org/wiki/Student%27s_t-test), also called \"Student's t-Test\", is typically used to determine if there is a significant difference between the means of some numeric variable between two groups. Here we'll show the syntax to do this test depending on whether the columns are in the same data frame.\n\n**Syntax 1:** This is the syntax when your numeric and categorical columns are in the same data frame. Provide the numeric column on the left side of the equation and the categorical column on the right side. Specify the dataset to `data = `. Optionally, set `paired = TRUE`, and `conf.level = ` (0.95 default), and `alternative = ` (either \"two.sided\", \"less\", or \"greater\"). Enter `?t.test` for more details. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## compare mean age by outcome group with a t-test\nt.test(age_years ~ gender, data = linelist)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n\tWelch Two Sample t-test\n\ndata: age_years by gender\nt = -21.344, df = 4902.3, p-value < 2.2e-16\nalternative hypothesis: true difference in means between group f and group m is not equal to 0\n95 percent confidence interval:\n -7.571920 -6.297975\nsample estimates:\nmean in group f mean in group m \n 12.60207 19.53701 \n```\n\n\n:::\n:::\n\n\n**Syntax 2:** You can compare two separate numeric vectors using this alternative syntax. For example, if the two columns are in different data sets. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(df1$age_years, df2$age_years)\n```\n:::\n\n\nYou can also use a t-test to determine whether a sample mean is significantly different from some specific value. Here we conduct a one-sample t-test with the known/hypothesized population mean as `mu = `: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(linelist$age_years, mu = 45)\n```\n:::\n\n\n### Shapiro-Wilk test {.unnumbered} \n\nThe [Shapiro-Wilk test](https://en.wikipedia.org/wiki/Shapiro%E2%80%93Wilk_test) can be used to determine whether a sample came from a normally-distributed population (an assumption of many other tests and analysis, such as the t-test). However, this can only be used on a sample between 3 and 5000 observations. For larger samples a [quantile-quantile plot](https://ggplot2.tidyverse.org/reference/geom_qq.html) may be helpful. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nshapiro.test(linelist$age_years)\n```\n:::\n\n\n### Wilcoxon rank sum test {.unnumbered}\n\nThe Wilcoxon rank sum test, also called the [Mann–Whitney U test](https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test), is often used to help determine if two numeric samples are from the same distribution when their populations are not normally distributed or have unequal variance.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n## compare age distribution by outcome group with a wilcox test\nwilcox.test(age_years ~ outcome, data = linelist)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n\tWilcoxon rank sum test with continuity correction\n\ndata: age_years by outcome\nW = 2501868, p-value = 0.8308\nalternative hypothesis: true location shift is not equal to 0\n```\n\n\n:::\n:::\n\n\n\n### Kruskal-Wallis test {.unnumbered}\n\n\nThe [Kruskal-Wallis test](https://en.wikipedia.org/wiki/Kruskal%E2%80%93Wallis_one-way_analysis_of_variance) is an extension of the Wilcoxon rank sum test that can be used to test for differences in the distribution of more than two samples. When only two samples are used it gives identical results to the Wilcoxon rank sum test. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## compare age distribution by outcome group with a kruskal-wallis test\nkruskal.test(age_years ~ outcome, linelist)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n\tKruskal-Wallis rank sum test\n\ndata: age_years by outcome\nKruskal-Wallis chi-squared = 0.045675, df = 1, p-value = 0.8308\n```\n\n\n:::\n:::\n\n\n### Chi-squared test {.unnumbered} \n\n[Pearson's Chi-squared test](https://en.wikipedia.org/wiki/Chi-squared_test) is used in testing for significant differences between categorical croups. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## compare the proportions in each group with a chi-squared test\nchisq.test(linelist$gender, linelist$outcome)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n\tPearson's Chi-squared test with Yates' continuity correction\n\ndata: linelist$gender and linelist$outcome\nX-squared = 0.0011841, df = 1, p-value = 0.9725\n```\n\n\n:::\n:::\n\n\n\n\n## **rstatix** package {}\n\nThe **rstatix** package offers the ability to run statistical tests and retrieve results in a \"pipe-friendly\" framework. The results are automatically in a data frame so that you can perform subsequent operations on the results. It is also easy to group the data being passed into the functions, so that the statistics are run for each group. \n\n\n### Summary statistics {.unnumbered} \n\nThe function `get_summary_stats()` is a quick way to return summary statistics. Simply pipe your dataset to this function and provide the columns to analyse. If no columns are specified, the statistics are calculated for all columns. \n\nBy default, a full range of summary statistics are returned: n, max, min, median, 25%ile, 75%ile, IQR, median absolute deviation (mad), mean, standard deviation, standard error, and a confidence interval of the mean. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>%\n rstatix::get_summary_stats(age, temp)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 13\n variable n min max median q1 q3 iqr mad mean sd se\n \n1 age 5802 0 84 13 6 23 17 11.9 16.1 12.6 0.166\n2 temp 5739 35.2 40.8 38.8 38.2 39.2 1 0.741 38.6 0.977 0.013\n# ℹ 1 more variable: ci \n```\n\n\n:::\n:::\n\n\nYou can specify a subset of summary statistics to return by providing one of the following values to `type = `: \"full\", \"common\", \"robust\", \"five_number\", \"mean_sd\", \"mean_se\", \"mean_ci\", \"median_iqr\", \"median_mad\", \"quantile\", \"mean\", \"median\", \"min\", \"max\". \n\nIt can be used with grouped data as well, such that a row is returned for each grouping-variable: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>%\n group_by(hospital) %>%\n rstatix::get_summary_stats(age, temp, type = \"common\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 12 × 11\n hospital variable n min max median iqr mean sd se ci\n \n 1 Central Hos… age 445 0 58 12 15 15.7 12.5 0.591 1.16 \n 2 Central Hos… temp 450 35.2 40.4 38.8 1 38.5 0.964 0.045 0.089\n 3 Military Ho… age 884 0 72 14 18 16.1 12.4 0.417 0.818\n 4 Military Ho… temp 873 35.3 40.5 38.8 1 38.6 0.952 0.032 0.063\n 5 Missing age 1441 0 76 13 17 16.0 12.9 0.339 0.665\n 6 Missing temp 1431 35.8 40.6 38.9 1 38.6 0.97 0.026 0.05 \n 7 Other age 873 0 69 13 17 16.0 12.5 0.422 0.828\n 8 Other temp 862 35.7 40.8 38.8 1.1 38.5 1.01 0.034 0.067\n 9 Port Hospit… age 1739 0 68 14 18 16.3 12.7 0.305 0.598\n10 Port Hospit… temp 1713 35.5 40.6 38.8 1.1 38.6 0.981 0.024 0.046\n11 St. Mark's … age 420 0 84 12 15 15.7 12.4 0.606 1.19 \n12 St. Mark's … temp 410 35.9 40.6 38.8 1.1 38.5 0.983 0.049 0.095\n```\n\n\n:::\n:::\n\n\nYou can also use **rstatix** to conduct statistical tests: \n\n### T-test {.unnumbered} \n\nUse a formula syntax to specify the numeric and categorical columns: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n t_test(age_years ~ gender)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 10\n .y. group1 group2 n1 n2 statistic df p p.adj p.adj.signif\n* \n1 age_… f m 2807 2803 -21.3 4902. 9.89e-97 9.89e-97 **** \n```\n\n\n:::\n:::\n\n\nOr use `~ 1` and specify `mu = ` for a one-sample T-test. This can also be done by group. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n t_test(age_years ~ 1, mu = 30)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 7\n .y. group1 group2 n statistic df p\n* \n1 age_years 1 null model 5802 -84.2 5801 0\n```\n\n\n:::\n:::\n\n\nIf applicable, the statistical tests can be done by group, as shown below: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(gender) %>% \n t_test(age_years ~ 1, mu = 18)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 3 × 8\n gender .y. group1 group2 n statistic df p\n* \n1 f age_years 1 null model 2807 -29.8 2806 7.52e-170\n2 m age_years 1 null model 2803 5.70 2802 1.34e- 8\n3 age_years 1 null model 192 -3.80 191 1.96e- 4\n```\n\n\n:::\n:::\n\n\n### Shapiro-Wilk test {.unnumbered} \n\nAs stated above, sample size must be between 3 and 5000. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n head(500) %>% # first 500 rows of case linelist, for example only\n shapiro_test(age_years)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 3\n variable statistic p\n \n1 age_years 0.917 6.67e-16\n```\n\n\n:::\n:::\n\n\n### Wilcoxon rank sum test {.unnumbered} \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n wilcox_test(age_years ~ gender)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 9\n .y. group1 group2 n1 n2 statistic p p.adj p.adj.signif\n* \n1 age_years f m 2807 2803 2829274 3.47e-74 3.47e-74 **** \n```\n\n\n:::\n:::\n\n\n\n### Kruskal-Wallis test {.unnumbered} \n\nAlso known as the Mann-Whitney U test. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n kruskal_test(age_years ~ outcome)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 6\n .y. n statistic df p method \n* \n1 age_years 5888 0.0457 1 0.831 Kruskal-Wallis\n```\n\n\n:::\n:::\n\n\n\n### Chi-squared test {.unnumbered} \n\nThe chi-square test function accepts a table, so first we create a cross-tabulation. There are many ways to create a cross-tabulation (see [Descriptive tables](tables_descriptive.qmd)) but here we use `tabyl()` from **janitor** and remove the left-most column of value labels before passing to `chisq_test()`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n tabyl(gender, outcome) %>% \n select(-1) %>% \n chisq_test()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 6\n n statistic p df method p.signif\n* \n1 5888 3.53 0.473 4 Chi-square test ns \n```\n\n\n:::\n:::\n\n\nMany many more functions and statistical tests can be run with **rstatix** functions. See the documentation for **rstatix** [online here](https://github.com/kassambara/rstatix) or by entering ?rstatix. \n\n\n\n\n\n## `gtsummary` package {#stats_gt}\n\nUse **gtsummary** if you are looking to add the results of a statistical test to a pretty table that was created with this package (as described in the **gtsummary** section of the [Descriptive tables](tables_descriptive.qmd#tbl_gt) page). \n\nPerforming statistical tests of comparison with `tbl_summary` is done by adding the \n`add_p` function to a table and specifying which test to use. It is possible to get p-values corrected for multiple testing by using the\n`add_q` function. Run `?tbl_summary` for details. \n\n### Chi-squared test {.unnumbered}\n\nCompare the proportions of a categorical variable in two groups. The default statistical test for `add_p()` when applied to a categorical variable is to perform a chi-squared test of independence with continuity correction, but if any expected call count is below 5 then a Fisher's exact test is used. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(gender, outcome) %>% # keep variables of interest\n tbl_summary(by = outcome) %>% # produce summary table and specify grouping variable\n add_p() # specify what test to perform\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n1323 missing rows in the \"outcome\" column have been removed.\nThe following errors were returned during `add_p()`:\n✖ For variable `gender` (`outcome`) and \"p.value\" statistic: The package\n \"cardx\" (>= 0.2.1) is required.\n```\n\n\n:::\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n

    Death
    \nN = 2,582

    \n
    1

    Recover
    \nN = 1,983

    \n
    1

    p-value

    \n
    gender


        f1,227 (50%)953 (50%)
        m1,228 (50%)950 (50%)
        Unknown12780
    1

    n (%)

    \n
    \n
    \n```\n\n:::\n:::\n\n\n\n### T-tests {.unnumbered} \n\nCompare the difference in means for a continuous variable in two groups. \nFor example, compare the mean age by patient outcome. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{mean} ({sd})\", # specify what statistics to show\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"t.test\") # specify what tests to perform\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n1323 missing rows in the \"outcome\" column have been removed.\nThe following errors were returned during `add_p()`:\n✖ For variable `age_years` (`outcome`) and \"p.value\" statistic: The package\n \"cardx\" (>= 0.2.1) is required.\n```\n\n\n:::\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n\n\n\n \n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n

    Death
    \nN = 2,582

    \n
    1

    Recover
    \nN = 1,983

    \n
    1

    p-value

    \n
    age_years16 (12)16 (13)
        Unknown3228
    1

    Mean (SD)

    \n
    \n
    \n```\n\n:::\n:::\n\n\n### Wilcoxon rank sum test{.unnumbered}\n\nCompare the distribution of a continuous variable in two groups. The default \nis to use the Wilcoxon rank sum test and the median (IQR) when comparing two \ngroups. However for non-normally distributed data or comparing multiple groups, \nthe Kruskal-wallis test is more appropriate. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{median} ({p25}, {p75})\", # specify what statistic to show (this is default so could remove)\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"wilcox.test\") # specify what test to perform (default so could leave brackets empty)\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n1323 missing rows in the \"outcome\" column have been removed.\nThe following errors were returned during `add_p()`:\n✖ For variable `age_years` (`outcome`) and \"p.value\" statistic: The package\n \"cardx\" (>= 0.2.1) is required.\n```\n\n\n:::\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n\n\n\n \n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n

    Death
    \nN = 2,582

    \n
    1

    Recover
    \nN = 1,983

    \n
    1

    p-value

    \n
    age_years13 (6, 23)13 (6, 23)
        Unknown3228
    1

    Median (Q1, Q3)

    \n
    \n
    \n```\n\n:::\n:::\n\n\n### Kruskal-wallis test {.unnumbered}\n\nCompare the distribution of a continuous variable in two or more groups, \nregardless of whether the data is normally distributed. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years, outcome) %>% # keep variables of interest\n tbl_summary( # produce summary table\n statistic = age_years ~ \"{median} ({p25}, {p75})\", # specify what statistic to show (default, so could remove)\n by = outcome) %>% # specify the grouping variable\n add_p(age_years ~ \"kruskal.test\") # specify what test to perform\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n1323 missing rows in the \"outcome\" column have been removed.\nThe following errors were returned during `add_p()`:\n✖ For variable `age_years` (`outcome`) and \"p.value\" statistic: The package\n \"cardx\" (>= 0.2.1) is required.\n```\n\n\n:::\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n \n\n\n\n \n\n\n\n \n \n \n \n \n \n \n

    Characteristic

    \n

    Death
    \nN = 2,582

    \n
    1

    Recover
    \nN = 1,983

    \n
    1

    p-value

    \n
    age_years13 (6, 23)13 (6, 23)
        Unknown3228
    1

    Median (Q1, Q3)

    \n
    \n
    \n```\n\n:::\n:::\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Correlations \n\nCorrelation between numeric variables can be investigated using the **tidyverse** \n**corrr** package. It allows you to compute correlations using Pearson, Kendall\ntau or Spearman rho. The package creates a table and also has a function to \nautomatically plot the values. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncorrelation_tab <- linelist %>% \n select(generation, age, ct_blood, days_onset_hosp, wt_kg, ht_cm) %>% # keep numeric variables of interest\n correlate() # create correlation table (using default pearson)\n\ncorrelation_tab # print\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 7\n term generation age ct_blood days_onset_hosp wt_kg ht_cm\n \n1 generation NA -2.22e-2 0.179 -0.288 -0.0302 -0.00942\n2 age -0.0222 NA 0.00849 -0.000635 0.833 0.877 \n3 ct_blood 0.179 8.49e-3 NA -0.600 -0.00636 0.0181 \n4 days_onset_hosp -0.288 -6.35e-4 -0.600 NA 0.0153 -0.00953\n5 wt_kg -0.0302 8.33e-1 -0.00636 0.0153 NA 0.884 \n6 ht_cm -0.00942 8.77e-1 0.0181 -0.00953 0.884 NA \n```\n\n\n:::\n\n```{.r .cell-code}\n## remove duplicate entries (the table above is mirrored) \ncorrelation_tab <- correlation_tab %>% \n shave()\n\n## view correlation table \ncorrelation_tab\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 7\n term generation age ct_blood days_onset_hosp wt_kg ht_cm\n \n1 generation NA NA NA NA NA NA\n2 age -0.0222 NA NA NA NA NA\n3 ct_blood 0.179 0.00849 NA NA NA NA\n4 days_onset_hosp -0.288 -0.000635 -0.600 NA NA NA\n5 wt_kg -0.0302 0.833 -0.00636 0.0153 NA NA\n6 ht_cm -0.00942 0.877 0.0181 -0.00953 0.884 NA\n```\n\n\n:::\n\n```{.r .cell-code}\n## plot correlations \nrplot(correlation_tab)\n```\n\n::: {.cell-output-display}\n![](stat_tests_files/figure-html/unnamed-chunk-20-1.png){width=672}\n:::\n:::\n\n\n\n\n\n## Resources { }\n\nMuch of the information in this page is adapted from these resources and vignettes online: \n\n[gtsummary](http://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html)\n\n[dplyr](https://dplyr.tidyverse.org/articles/grouping.html)\n\n[corrr](https://corrr.tidymodels.org/articles/using-corrr.html)\n\n[sthda correlation](http://www.sthda.com/english/wiki/correlation-test-between-two-variables-in-r)\n", + "supporting": [ + "stat_tests_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-in-header": [ + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/new_pages/stat_tests_files/figure-html/unnamed-chunk-20-1.png b/new_pages/stat_tests_files/figure-html/unnamed-chunk-20-1.png new file mode 100644 index 00000000..6dce6815 Binary files /dev/null and b/new_pages/stat_tests_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/new_pages/survey_analysis.qmd b/new_pages/survey_analysis.qmd index d00d2dd4..4158c7ee 100644 --- a/new_pages/survey_analysis.qmd +++ b/new_pages/survey_analysis.qmd @@ -33,13 +33,13 @@ eventually have a section on sampling frames as well as sample size calculations -1. Survey data -2. Observation time -3. Weighting -4. Survey design objects -5. Descriptive analysis -6. Weighted proportions -7. Weighted rates +1. Survey data. +2. Observation time. +3. Weighting. +4. Survey design objects. +5. Descriptive analysis. +6. Weighted proportions. +7. Weighted rates. @@ -76,9 +76,9 @@ pacman::p_load_gh( The example dataset used in this section: -- fictional mortality survey data. -- fictional population counts for the survey area. -- data dictionary for the fictional mortality survey data. +- Fictional mortality survey data. +- Fictional population counts for the survey area. +- Data dictionary for the fictional mortality survey data. This is based off the MSF OCA ethical review board pre-approved survey. The fictional dataset was produced as part of the ["R4Epis" project](https://r4epis.netlify.app/). @@ -182,7 +182,7 @@ are in. Finally, we recode all of the yes/no variables to TRUE/FALSE variables - otherwise these cant be used by the **survey** proportion functions. -```{r cleaning} +```{r cleaning, warning=F, message=F} ## select the date variable names from the dictionary DATEVARS <- survey_dict %>% @@ -243,31 +243,6 @@ we will demonstrate code for: - Cluster - Stratified and cluster -As described above (depending on how you design your questionnaire) the data for -each level would be exported as a separate dataset from Kobo. In our example there -is one level for households and one level for individuals within those households. - -These two levels are linked by a unique identifier. -For a Kobo dataset this variable is "_index" at the household level, which -matches the "_parent_index" at the individual level. -This will create new rows for household with each matching individual, -see the handbook section on [joining](https://epirhandbook.com/joining-data.html) -for details. - -```{r merge_data_levels, eval = FALSE} - -## join the individual and household data to form a complete data set -survey_data <- left_join(survey_data_hh, - survey_data_indiv, - by = c("_index" = "_parent_index")) - - -## create a unique identifier by combining indeces of the two levels -survey_data <- survey_data %>% - mutate(uid = str_glue("{index}_{index_y}")) - -``` - ## Observation time { } @@ -320,14 +295,14 @@ We can use the `find_start_date()` function from **sitrep** to fine the causes f the dates and then use that to calculate the difference between days (person-time). start date: -Earliest appropriate arrival event within your recall period +Earliest appropriate arrival event within your recall period. Either the beginning of your recall period (which you define in advance), or a -date after the start of recall if applicable (e.g. arrivals or births) +date after the start of recall if applicable (e.g. arrivals or births). end date: -Earliest appropriate departure event within your recall period +Earliest appropriate departure event within your recall period. Either the end of your recall period, or a date before the end of recall -if applicable (e.g. departures, deaths) +if applicable (e.g. departures, deaths). ```{r observation_time} @@ -459,19 +434,18 @@ survey_data <- survey_data %>% ## Survey design objects { } -Create survey object according to your study design. -Used the same way as data frames to calculate weight proportions etc. -Make sure that all necessary variables are created before this. +Next you should create a survey object according to your study design. This is used in the same way as data frames to calculate weight proportions and other aspects of survey analysis. You should make sure all your necessary variables are created before this. -There are four options, comment out those you do not use: +There are four options: - Simple random - Stratified - Cluster - Stratified cluster -For this template - we will pretend that we cluster surveys in two separate +In the template below - we will pretend that we cluster surveys in two separate strata (health districts A and B). -So to get overall estimates we need have combined cluster and strata weights. + +To get overall estimates we need to have combined cluster and strata weights. As mentioned previously, there are two packages available for doing this. The classic one is **survey** and then there is a wrapper package called **srvyr** @@ -572,9 +546,9 @@ alluvial/sankey diagrams. In general, you should consider including the following descriptive analyses: -- Final number of clusters, households and individuals included -- Number of excluded individuals and the reasons for exclusion -- Median (range) number of households per cluster and individuals per household +- Final number of clusters, households and individuals included. +- Number of excluded individuals and the reasons for exclusion. +- Median (range) number of households per cluster and individuals per household. ### Sampling bias @@ -646,11 +620,11 @@ left_join(ag, propcount, by = "age_group", suffix = c("", "_pop")) %>% Demographic (or age-sex) pyramids are an easy way of visualising the distribution in your survey population. It is also worth considering creating -[descriptive tables](https://epirhandbook.com/descriptive-tables.html) of age +[descriptive tables](tables_descriptive.qmd) of age and sex by survey strata. We will demonstrate using the **apyramid** package as it allows for weighted proportions using our survey design object created above. Other options for creating -[demographic pyramids](https://epirhandbook.com/demographic-pyramids-and-likert-scales.html) +[demographic pyramids](age_pyramid.qmd) are covered extensively in that chapter of the handbook. We will also use a wrapper function from **apyramid** called `age_pyramid()` which saves a few lines of coding for producing a plot with proportions. @@ -823,12 +797,9 @@ with associated confidence intervals and design effect. There are four different options using functions from the following packages: **survey**, **srvyr**, **sitrep** and **gtsummary**. For minimal coding to produce a standard epidemiology style table, we would -recommend the **sitrep** function - which is a wrapper for **srvyr** code; note -however that this is not yet on CRAN and may change in the future. +recommend the **sitrep** function - which is a wrapper for **srvyr** code. Otherwise, the **survey** code is likely to be the most stable long-term, whereas -**srvyr** will fit most nicely within tidyverse work-flows. While **gtsummary** -functions hold a lot of potential, they appear to be experimental and incomplete -at the time of writing. +**srvyr** will fit most nicely within tidyverse work-flows. As another alternative, **gtsummary** offers an easy way to display survey data following its update to versions >=2.0. ### **Survey** package @@ -993,67 +964,48 @@ purrr::map( ``` - - ### **Sitrep** package The `tab_survey()` function from **sitrep** is a wrapper for **srvyr**, allowing you to create weighted tables with minimal coding. It also allows you to calculate weighted proportions for categorical variables. -```{r sitrep_props} +```{r eval = F, sitrep_props} ## using the survey design object survey_design %>% ## pass the names of variables of interest unquoted - tab_survey(arrived, left, died, education_level, - deff = TRUE, # calculate the design effect - pretty = TRUE # merge the proportion and 95%CI - ) + tab_survey( + "arrived", + "left", + "died", + "education_level", + deff = TRUE, # calculate the design effect + pretty = TRUE # merge the proportion and 95%CI + ) ``` - +```{r, echo = F, warning = F, message=FALSE} +import(here("data", "surveys", "tab_survey_output.rds")) +``` ### **Gtsummary** package -With **gtsummary** there does not seem to be inbuilt functions yet to add confidence -intervals or design effect. -Here we show how to define a function for adding confidence intervals and then -add confidence intervals to a **gtsummary** table created using the `tbl_svysummary()` -function. - +With **gtsummary** we can use the function `tbl_svysummary()` and the addition `add_ci()` to add confidence intervals. ```{r gtsummary_table} - -confidence_intervals <- function(data, variable, by, ...) { - - ## extract the confidence intervals and multiply to get percentages - props <- svyciprop(as.formula(paste0( "~" , variable)), - data, na.rm = TRUE) - - ## extract the confidence intervals - as.numeric(confint(props) * 100) %>% ## make numeric and multiply for percentage - round(., digits = 1) %>% ## round to one digit - c(.) %>% ## extract the numbers from matrix - paste0(., collapse = "-") ## combine to single character -} - ## using the survey package design object -tbl_svysummary(base_survey_design, +tbl_svysummary(data = base_survey_design, include = c(arrived, left, died), ## define variables want to include - statistic = list(everything() ~ c("{n} ({p}%)"))) %>% ## define stats of interest - add_n() %>% ## add the weighted total - add_stat(fns = everything() ~ confidence_intervals) %>% ## add CIs - ## modify the column headers - modify_header( - list( - n ~ "**Weighted total (N)**", - stat_0 ~ "**Weighted Count**", - add_stat_1 ~ "**95%CI**" - ) - ) + statistic = list(everything() ~ "{n} ({p}%)")) %>% ## define stats of interest + add_ci() %>% ## add confidence intervals + add_n() %>% + modify_header(label = list( + n ~ "**Weighted total (N)**", + stat_0 ~ "**Weighted count**" + )) ``` @@ -1089,7 +1041,7 @@ cbind( ### **Srvyr** package -```{r srvyr_ratio} +```{r srvyr_ratio, warning=FALSE, message=F} survey_design %>% ## survey ratio used to account for observation time @@ -1102,7 +1054,43 @@ survey_design %>% ``` +## Weighted regression + +Another tool we can use to analyse our survey data is to use weighted regression. This allows us to carry out to account for the survey design in our regression in order to avoid biases that may be introduced from the survey process. + +To carry out a *univariate regression*, we can use the packages **survey** for the function `svyglm()` and the package **gtsummary** which allows us to call `svyglm()` inside the function `tbl_uvregression`. To do this we first use the `survey_design` object created above. This is then provided to the function `tbl_uvregression()` as in the [Univariate and multivariable regression chapter](regression.qmd). We then make one key change, we change `method = glm` to `method = survey::svyglm` in order to carry out our survey weighted regression. + +Here we will be using the previously created object `survey_design` to predict whether the value in the column `died` is `TRUE`, using the columns `malaria_treatment`, `bednet`, and `age_years`. +```{r, warning=F, message=F} + +survey_design %>% + tbl_uvregression( #Carry out a univariate regression, if we wanted a multivariable regression we would use tbl_ + method = survey::svyglm, #Set this to survey::svyglm to carry out our weighted regression on the survey data + y = died, #The column we are trying to predict + method.args = list(family = binomial), #The family, we are carrying out a logistic regression so we want the family as binomial + include = c(malaria_treatment, #These are the columns we want to evaluate + bednet, + age_years), + exponentiate = T #To transform the log odds to odds ratio for easier interpretation + ) + +``` + +If we wanted to carry out a *multivariable* regression, we would have to first use the function `svyglm()` and pipe (`%>%`) the results into the function `tbl_regression`. Note that we need to specify the formula. + +```{r, warning=F, message=F} + +survey_design %>% + svyglm(formula = died ~ malaria_treatment + + bednet + + age_years, + family = binomial) %>% #The family, we are carrying out a logistic regression so we want the family as binomial + tbl_regression( + exponentiate = T #To transform the log odds to odds ratio for easier interpretation + ) + +``` @@ -1117,3 +1105,5 @@ survey_design %>% [gtsummary package](http://www.danieldsjoberg.com/gtsummary/reference/index.html) [EPIET survey case studies](https://github.com/EPIET/RapidAssessmentSurveys) + +[Analyzing Complex Survey Data](https://www.bookdown.org/rwnahhas/RMPH/survey.html) diff --git a/new_pages/survival_analysis.qmd b/new_pages/survival_analysis.qmd index 59dc61c9..7db57a21 100644 --- a/new_pages/survival_analysis.qmd +++ b/new_pages/survival_analysis.qmd @@ -13,13 +13,13 @@ knitr::include_graphics(here::here("images", "survival_analysis.png")) ## Overview {} -*Survival analysis* focuses on describing for a given individual or group of individuals, a defined point of event called **_the failure_** (occurrence of a disease, cure from a disease, death, relapse after response to treatment...) that occurs after a period of time called **_failure time_** (or **_follow-up time_** in cohort/population-based studies) during which individuals are observed. To determine the failure time, it is then necessary to define a time of origin (that can be the inclusion date, the date of diagnosis...). +*Survival analysis* focuses on describing for a given individual or group of individuals, a defined point of event called **_the failure_** (occurrence of a disease, cure from a disease, death, relapse after response to treatment...) that occurs after a period of time called **_failure time_** (or **_follow-up time_** in cohort/population-based studies) during which individuals are observed. To determine the failure time, it is then necessary to define a time of origin (that can be the inclusion date, the date of diagnosis, etc.). The target of inference for survival analysis is then the time between an origin and an event. In current medical research, it is widely used in clinical studies to assess the effect of a treatment for instance, or in cancer epidemiology to assess a large variety of cancer survival measures. -It is usually expressed through the **_survival probability_** which is the probability that the event of interest has not occurred by a duration t. +It is usually expressed through the **_survival probability_** which is the probability that the event of interest has not occurred by a duration *t*. **_Censoring_**: Censoring occurs when at the end of follow-up, some of the individuals have not had the event of interest, and thus their true time to event is unknown. We will mostly focus on right censoring here but for more details on censoring and survival analysis in general, you can see references. @@ -67,9 +67,9 @@ This page explores survival analyses using the linelist used in most of the prev ### Import dataset {.unnumbered} -We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). +We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r echo=F} +```{r echo=F, message=F, warning=F} # import linelist linelist_case_data <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -83,7 +83,7 @@ linelist_case_data <- rio::import("linelist_cleaned.rds") In short, survival data can be described as having the following three characteristics: -1) the dependent variable or response is the waiting time until the occurrence of a well-defined event, +1) The dependent variable or response is the waiting time until the occurrence of a well-defined event, 2) observations are censored, in the sense that for some units the event of interest has not occurred at the time the data are analyzed, and 3) there are predictors or explanatory variables whose effect on the waiting time we wish to assess or control. @@ -91,8 +91,8 @@ Thus, we will create different variables needed to respect that structure and ru We define: -- a new data frame `linelist_surv` for this analysis -- our event of interest as being "death" (hence our survival probability will be the probability of being alive after a certain time after the time of origin), +- A new data frame `linelist_surv` for this analysis. +- The event of interest as being "death" (hence our survival probability will be the probability of being alive after a certain time after the time of origin), - the follow-up time (`futime`) as the time between the time of onset and the time of outcome *in days*, - censored patients as those who recovered or for whom the final outcome is not known ie the event "death" was not observed (`event=0`). @@ -145,7 +145,7 @@ linelist_surv %>% tabyl(outcome, event) ``` -Now we cross-tabulate the new age_cat_small var and the old age_cat col to ensure correct assingments +Now we cross-tabulate the new age_cat_small var and the old age_cat col to ensure correct assignments. ```{r} linelist_surv %>% @@ -243,16 +243,16 @@ head(survobj, 10) ### Running initial analyses {.unnumbered} -We then start our analysis using the `survfit()` function to produce a *survfit object*, which fits the default calculations for **_Kaplan Meier_** (KM) estimates of the overall (marginal) survival curve, which are in fact a step function with jumps at observed event times. The final *survfit object* contains one or more survival curves and is created using the *Surv* object as a response variable in the model formula. +We then start our analysis using the `survfit()` function to produce a *survfit object*, which fits the default calculations for [**_Kaplan Meier_** (KM)](https://en.wikipedia.org/wiki/Kaplan%E2%80%93Meier_estimator) estimates of the overall (marginal) survival curve, which are in fact a step function with jumps at observed event times. The final *survfit object* contains one or more survival curves and is created using the *Surv* object as a response variable in the model formula. **_NOTE:_** The Kaplan-Meier estimate is a nonparametric maximum likelihood estimate (MLE) of the survival function. . (see resources for more information). The summary of this *survfit object* will give what is called a *life table*. For each time step of the follow-up (`time`) where an event happened (in ascending order): -* the number of people who were at risk of developing the event (people who did not have the event yet nor were censored: `n.risk`) -* those who did develop the event (`n.event`) -* and from the above: the probability of *not* developing the event (probability of not dying, or of surviving past that specific time) -* finally, the standard error and the confidence interval for that probability are derived and displayed +* The number of people who were at risk of developing the event (people who did not have the event yet nor were censored: `n.risk`). +* Those who did develop the event (`n.event`), +* and from the above: the probability of *not* developing the event (probability of not dying, or of surviving past that specific time). +* Finally, the standard error and the confidence interval for that probability are derived and displayed. We fit the KM estimates using the formula where the previously Surv object "survobj" is the response variable. "~ 1" precises we run the model for the overall survival. @@ -363,7 +363,7 @@ legend( ## Comparison of survival curves -To compare the survival within different groups of our observed participants or patients, we might need to first look at their respective survival curves and then run tests to evaluate the difference between independent groups. This comparison can concern groups based on gender, age, treatment, comorbidity... +To compare the survival within different groups of our observed participants or patients, we might need to first look at their respective survival curves and then run tests to evaluate the difference between independent groups. This comparison can concern groups based on gender, age, treatment, comorbidity, etc. ### Log rank test {.unnumbered} @@ -495,7 +495,7 @@ In a Cox proportional hazards regression model, the measure of effect is the **_ We can first fit a model to assess the effect of age and gender on the survival. By just printing the model, we have the information on: - + the estimated regression coefficients `coef` which quantifies the association between the predictors and the outcome, + + The estimated regression coefficients `coef` which quantifies the association between the predictors and the outcome, + their exponential (for interpretability, `exp(coef)`) which produces the *hazard ratio*, + their standard error `se(coef)`, + the z-score: how many standard errors is the estimated coefficient away from 0, @@ -607,7 +607,7 @@ ggforest(linelistsurv_cox, data = linelist_surv) ## Time-dependent covariates in survival models {} -Some of the following sections have been adapted with permission from an excellent [introduction to survival analysis in R](https://www.emilyzabor.com/tutorials/survival_analysis_in_r_tutorial.html) by [Dr. Emily Zabor](https://www.emilyzabor.com/) +Some of the following sections have been adapted with permission from an excellent [introduction to survival analysis in R](https://www.emilyzabor.com/tutorials/survival_analysis_in_r_tutorial.html) by [Dr. Emily Zabor](https://www.emilyzabor.com/). In the last section we covered using Cox regression to examine associations between covariates of interest and survival outcomes.But these analyses rely on the covariate being measured at baseline, that is, before follow-up time for the event begins. @@ -621,12 +621,12 @@ Analysis of time-dependent covariates in R requires setup of a special dataset. For this, we'll use a new dataset from the `SemiCompRisks` package named `BMT`, which includes data on 137 bone marrow transplant patients. The variables we'll focus on are: -* `T1` - time (in days) to death or last follow-up -* `delta1` - death indicator; 1-Dead, 0-Alive -* `TA` - time (in days) to acute graft-versus-host disease +* `T1` - time (in days) to death or last follow-up. +* `delta1` - death indicator; 1-Dead, 0-Alive. +* `TA` - time (in days) to acute graft-versus-host disease. * `deltaA` - acute graft-versus-host disease indicator; - * 1 - Developed acute graft-versus-host disease - * 0 - Never developed acute graft-versus-host disease + * 1 - Developed acute graft-versus-host disease. + * 0 - Never developed acute graft-versus-host disease. We'll load this dataset from the **survival** package using the **base** R command `data()`, which can be used for loading data that is already included in a R package that is loaded. The data frame `BMT` will appear in your R environment. @@ -652,9 +652,9 @@ DT::datatable(bmt, rownames = FALSE, options = list(pageLength = 5, scrollX=T), Next, we'll use the `tmerge()` function with the `event()` and `tdc()` helper functions to create the restructured dataset. Our goal is to restructure the dataset to create a separate row for each patient for each time interval where they have a different value for `deltaA`. In this case, each patient can have at most two rows depending on whether they developed acute graft-versus-host disease during the data collection period. We'll call our new indicator for the development of acute graft-versus-host disease `agvhd`. -- `tmerge()` creates a long dataset with multiple time intervals for the different covariate values for each patient -- `event()` creates the new event indicator to go with the newly-created time intervals -- `tdc()` creates the time-dependent covariate column, `agvhd`, to go with the newly created time intervals +- `tmerge()` creates a long dataset with multiple time intervals for the different covariate values for each patient. +- `event()` creates the new event indicator to go with the newly-created time intervals. +- `tdc()` creates the time-dependent covariate column, `agvhd`, to go with the newly created time intervals. ```{r} td_dat <- @@ -719,11 +719,9 @@ As you can see from the forest plot, confidence interval, and p-value, there doe [Survival analysis in infectious disease research: Describing events in time](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2954271/) -[Chapter on advanced survival models Princeton](https://data.princeton.edu/wws509/notes/c7.pdf) - [Using Time Dependent Covariates and Time Dependent Coefficients in the Cox Model](https://cran.r-project.org/web/packages/survival/vignettes/timedep.pdf) -[Survival analysis cheatsheet R](https://publicifsv.sund.ku.dk/~ts/survival/survival-cheat.pdf) +[Survival analysis cheatsheet R](https://bioconnector.github.io/workshops/handouts/r-survival-cheatsheet.pdf) [Survminer cheatsheet](https://paulvanderlaken.files.wordpress.com/2017/08/survminer_cheatsheet.pdf) diff --git a/new_pages/tables_descriptive.qmd b/new_pages/tables_descriptive.qmd index dcf1d4e0..640718f4 100644 --- a/new_pages/tables_descriptive.qmd +++ b/new_pages/tables_descriptive.qmd @@ -13,11 +13,11 @@ Each of these packages has advantages and disadvantages in the areas of code sim You have several choices when producing tabulation and cross-tabulation summary tables. Some of the factors to consider include code simplicity, customizeability, the desired output (printed to R console, as data frame, or as "pretty" .png/.jpeg/.html image), and ease of post-processing. Consider the points below as you choose the tool for your situation. -* Use `tabyl()` from **janitor** to produce and "adorn" tabulations and cross-tabulations -* Use `get_summary_stats()` from **rstatix** to easily generate data frames of numeric summary statistics for multiple columns and/or groups -* Use `summarise()` and `count()` from **dplyr** for more complex statistics, tidy data frame outputs, or preparing data for `ggplot()` -* Use `tbl_summary()` from **gtsummary** to produce detailed publication-ready tables -* Use `table()` from **base** R if you do not have access to the above packages +* Use `tabyl()` from **janitor** to produce and "adorn" tabulations and cross-tabulations. +* Use `get_summary_stats()` from **rstatix** to easily generate data frames of numeric summary statistics for multiple columns and/or groups. +* Use `summarise()` and `count()` from **dplyr** for more complex statistics, tidy data frame outputs, or preparing data for `ggplot()`. +* Use `tbl_summary()` from **gtsummary** to produce detailed publication-ready tables. +* Use `table()` from **base** R if you do not have access to the above packages. @@ -45,9 +45,9 @@ pacman::p_load( ### Import data {.unnumbered} -We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). +We import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, warning=F, message=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -165,7 +165,7 @@ Use **janitor**'s "adorn" functions to add totals or convert to proportions, per Function | Outcome -------------------|-------------------------------- `adorn_totals()` | Adds totals (`where = ` "row", "col", or "both"). Set `name =` for "Total". -`adorn_percentages()` | Convert counts to proportions, with `denominator = ` "row", "col", or "all" +`adorn_percentages()` | Convert counts to proportions, with `denominator = ` "row", "col", or "all". `adorn_pct_formatting()` | Converts proportions to percents. Specify `digits =`. Remove the "%" symbol with `affix_sign = FALSE`. `adorn_rounding()` | To round proportions to `digits =` places. To round percents use `adorn_pct_formatting()` with `digits = `. `adorn_ns()` | Add counts to a table of proportions or percents. Indicate `position =` "rear" to show counts in parentheses, or "front" to put the percents in parentheses. @@ -281,7 +281,7 @@ See the page on [Simple statistical tests](stat_tests.qmd) for more code and tip ### Other tips {.unnumbered} * Include the argument `na.rm = TRUE` to exclude missing values from any of the above calculations. -* If applying any `adorn_*()` helper functions to tables not created by `tabyl()`, you can specify particular column(s) to apply them to like `adorn_percentage(,,,c(cases,deaths))` (specify them to the 4th unnamed argument). The syntax is not simple. Consider using `summarise()` instead. +* If applying any `adorn_*()` helper functions to tables not created by `tabyl()`, you can specify particular column(s) to apply them to like. `adorn_percentage(,,,c(cases,deaths))` (specify them to the 4th unnamed argument). The syntax is not simple. Consider using `summarise()` instead. * You can read more detail in the [janitor page](https://cran.r-project.org/web/packages/janitor/vignettes/janitor.html) and this [tabyl vignette](https://cran.r-project.org/web/packages/janitor/vignettes/tabyls.html). @@ -316,9 +316,9 @@ linelist %>% The above command can be shortened by using the `count()` function instead. `count()` does the following: -1) Groups the data by the columns provided to it -2) Summarises them with `n()` (creating column `n`) -3) Un-groups the data +1) Groups the data by the columns provided to it. +2) Summarises them with `n()` (creating column `n`). +3) Un-groups the data. ```{r} linelist %>% @@ -358,7 +358,8 @@ To easily display percents, you can wrap the proportion in the function `percent age_summary <- linelist %>% count(age_cat) %>% # group and count by gender (produces "n" column) mutate( # create percent of column - note the denominator - percent = scales::percent(n / sum(n))) + percent = scales::percent(n / sum(n)) + ) # print age_summary @@ -428,12 +429,12 @@ summary_table # print Some tips: -* Use `sum()` with a logic statement to "count" rows that meet certain criteria (`==`) -* Note the use of `na.rm = TRUE` within mathematical functions like `sum()`, otherwise `NA` will be returned if there are any missing values -* Use the function `percent()` from the **scales** package to easily convert to percents - * Set `accuracy = ` to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively -* Use `round()` from **base** R to specify decimals -* To calculate these statistics on the entire dataset, use `summarise()` without `group_by()` +* Use `sum()` with a logic statement to "count" rows that meet certain criteria (`==`). +* Note the use of `na.rm = TRUE` within mathematical functions like `sum()`, otherwise `NA` will be returned if there are any missing values. +* Use the function `percent()` from the **scales** package to easily convert to percents. + * Set `accuracy = ` to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively. +* Use `round()` from **base** R to specify decimals. +* To calculate these statistics on the entire dataset, use `summarise()` without `group_by()`. * You may create columns for the purposes of later calculations (e.g. denominators) that you eventually drop from your data frame with `select()`. @@ -484,7 +485,7 @@ summary_table %>% *Percentiles* and quantiles in **dplyr** deserve a special mention. To return quantiles, use `quantile()` with the defaults or specify the value(s) you would like with `probs = `. -```{r} +```{r, warning=FALSE, message=F} # get default percentile values of age (0%, 25%, 50%, 75%, 100%) linelist %>% summarise(age_percentiles = quantile(age_years, na.rm = TRUE)) @@ -563,14 +564,14 @@ linelist_agg %>% You can use `summarise()` across multiple columns using `across()`. This makes life easier when you want to calculate the same statistics for many columns. Place `across()` within `summarise()` and specify the following: -* `.cols = ` as either a vector of column names `c()` or "tidyselect" helper functions (explained below) -* `.fns = ` the function to perform (no parentheses) - you can provide multiple within a `list()` +* `.cols = ` as either a vector of column names `c()` or "tidyselect" helper functions (explained below). +* `.fns = ` the function to perform (no parentheses) - you can provide multiple within a `list()`. Below, `mean()` is applied to several numeric columns. A vector of columns are named explicitly to `.cols = ` and a single function `mean` is specified (no parentheses) to `.fns = `. Any additional arguments for the function (e.g. `na.rm=TRUE`) are provided after `.fns = `, separated by a comma. It can be difficult to get the order of parentheses and commas correct when using `across()`. Remember that within `across()` you must include the columns, the functions, and any extra arguments needed for the functions. -```{r} +```{r, warning=F, message=F} linelist %>% group_by(outcome) %>% summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns @@ -580,7 +581,7 @@ linelist %>% Multiple functions can be run at once. Below the functions `mean` and `sd` are provided to `.fns = ` within a `list()`. You have the opportunity to provide character names (e.g. "mean" and "sd") which are appended in the new column names. -```{r} +```{r, warning=F, message=F} linelist %>% group_by(outcome) %>% summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns @@ -590,15 +591,15 @@ linelist %>% Here are those "tidyselect" helper functions you can provide to `.cols = ` to select columns: -* `everything()` - all other columns not mentioned -* `last_col()` - the last column -* `where()` - applies a function to all columns and selects those which are TRUE -* `starts_with()` - matches to a specified prefix. Example: `starts_with("date")` -* `ends_with()` - matches to a specified suffix. Example: `ends_with("_end")` -* `contains()` - columns containing a character string. Example: `contains("time")` -* `matches()` - to apply a regular expression (regex). Example: `contains("[pt]al")` -* `num_range()` - -* `any_of()` - matches if column is named. Useful if the name might not exist. Example: `any_of(date_onset, date_death, cardiac_arrest)` +* `everything()` - all other columns not mentioned. +* `last_col()` - the last column. +* `where()` - applies a function to all columns and selects those which are `TRUE`. +* `starts_with()` - matches to a specified prefix. Example: `starts_with("date")`. +* `ends_with()` - matches to a specified suffix. Example: `ends_with("_end")`. +* `contains()` - columns containing a character string. Example: `contains("time")`. +* `matches()` - to apply a regular expression (regex). Example: `contains("[pt]al")`. +* `num_range()` - matches a numerical range. Example: `num_range("wk", 1:5)` would select columns with the prefix "wk" and the number 1:5 after. +* `any_of()` - matches if column is named. Useful if the name might not exist. Example: `any_of(date_onset, date_death, cardiac_arrest)`. For example, to return the mean of every numeric column use `where()` and provide the function `as.numeric()` (without parentheses). All this remains within the `across()` command. @@ -674,8 +675,9 @@ by_hospital <- linelist %>% filter(!is.na(outcome) & hospital != "Missing") %>% # Remove cases with missing outcome or hospital group_by(hospital, outcome) %>% # Group data summarise( # Create new summary columns of indicators of interest - N = n(), # Number of rows per hospital-outcome group - ct_value = median(ct_blood, na.rm=T)) # median CT value per group + N = n(), # Number of rows per hospital-outcome group + ct_value = median(ct_blood, na.rm=T) # median CT value per group + ) by_hospital # print table ``` @@ -831,7 +833,7 @@ table If you want to print your summary statistics in a pretty, publication-ready graphic, you can use the **gtsummary** package and its function `tbl_summary()`. The code can seem complex at first, but the outputs look very nice and print to your RStudio Viewer panel as an HTML image. Read a [vignette here](http://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html). -You can also add the results of statistical tests to **gtsummary** tables. This process is described in the **gtsummary** section of the [Simple statistical tests](#stats_gt) page. +You can also add the results of statistical tests to **gtsummary** tables. This process is described in the **gtsummary** section of the [Simple statistical tests](descriptive_statistics.qmd) page. To introduce `tbl_summary()` we will show the most basic behavior first, which actually produces a large and beautiful table. Then, we will examine in detail how to make adjustments and more tailored tables. @@ -893,8 +895,8 @@ Adjust how missing values are displayed. The default is "Unknown". **`type = `** This is used to adjust how many levels of the statistics are shown. The syntax is similar to `statistic = ` in that you provide an equation with columns on the left and a value on the right. Two common scenarios include: -* `type = all_categorical() ~ "categorical"` Forces dichotomous columns (e.g. `fever` yes/no) to show all levels instead of only the “yes” row -* `type = all_continuous() ~ "continuous2"` Allows multi-line statistics per variable, as shown in a later section +* `type = all_categorical() ~ "categorical"` Forces dichotomous columns (e.g. `fever` yes/no) to show all levels instead of only the “yes” row. +* `type = all_continuous() ~ "continuous2"` Allows multi-line statistics per variable, as shown in a later section. In the example below, each of these arguments is used to modify the original summary table: @@ -908,7 +910,6 @@ linelist %>% digits = all_continuous() ~ 1, # rounding for continuous columns type = all_categorical() ~ "categorical", # force all categorical levels to display label = list( # display labels for column names - outcome ~ "Outcome", age_years ~ "Age (years)", gender ~ "Gender", temp ~ "Temperature", @@ -934,6 +935,18 @@ linelist %>% "{min}, {max}") # line 3: min and max ) ``` + +### `tbl_wide_summary()` + +You may also want to display your results in *wide* format, rather than *long*. To do so in **gtsummary** you can use the function `tbl_wide_summary()`. + +```{r} +linelist %>% + select(age_years, temp) %>% + tbl_wide_summary() +``` + + There are many other ways to modify these tables, including adding p-values, adjusting color and headings, etc. Many of these are described in the documentation (enter `?tbl_summary` in Console), and some are given in the section on [statistical tests](https://epirhandbook.com/simple-statistical-tests.html). @@ -983,9 +996,9 @@ addmargins(age_by_outcome) Converting a `table()` object directly to a data frame is not straight-forward. One approach is demonstrated below: 1) Create the table, *without using* `useNA = "always"`. Instead convert `NA` values to "(Missing)" with `fct_explicit_na()` from **forcats**. -2) Add totals (optional) by piping to `addmargins()` -3) Pipe to the **base** R function `as.data.frame.matrix()` -4) Pipe the table to the **tibble** function `rownames_to_column()`, specifying the name for the first column +2) Add totals (optional) by piping to `addmargins()`. +3) Pipe to the **base** R function `as.data.frame.matrix()`. +4) Pipe the table to the **tibble** function `rownames_to_column()`, specifying the name for the first column. 5) Print, View, or export as desired. In this example we use `flextable()` from package **flextable** as described in the [Tables for presentation](tables_presentation.qmd) page. This will print to the RStudio viewer pane as a pretty HTML image. ```{r, warning=F, message=F} diff --git a/new_pages/tables_descriptive_files/execute-results/html.json b/new_pages/tables_descriptive_files/execute-results/html.json new file mode 100644 index 00000000..498a13f7 --- /dev/null +++ b/new_pages/tables_descriptive_files/execute-results/html.json @@ -0,0 +1,21 @@ +{ + "hash": "ba45fb6735cd37a96ea1100fa580adcb", + "result": { + "engine": "knitr", + "markdown": "# Descriptive tables {#tables_descriptive}\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/descriptive_tables.png){fig-align='center' width=75%}\n:::\n:::\n\n\nThis page demonstrates the use of **janitor**, **dplyr**, **gtsummary**, **rstatix**, and **base** R to summarise data and create tables with descriptive statistics. \n\n*This page covers how to *create* the underlying tables, whereas the [Tables for presentation](tables_presentation.qmd) page covers how to nicely format and print them.* \n\nEach of these packages has advantages and disadvantages in the areas of code simplicity, accessibility of outputs, quality of printed outputs. Use this page to decide which approach works for your scenario. \n\n\nYou have several choices when producing tabulation and cross-tabulation summary tables. Some of the factors to consider include code simplicity, customizeability, the desired output (printed to R console, as data frame, or as \"pretty\" .png/.jpeg/.html image), and ease of post-processing. Consider the points below as you choose the tool for your situation.\n\n* Use `tabyl()` from **janitor** to produce and \"adorn\" tabulations and cross-tabulations \n* Use `get_summary_stats()` from **rstatix** to easily generate data frames of numeric summary statistics for multiple columns and/or groups \n* Use `summarise()` and `count()` from **dplyr** for more complex statistics, tidy data frame outputs, or preparing data for `ggplot()` \n* Use `tbl_summary()` from **gtsummary** to produce detailed publication-ready tables \n* Use `table()` from **base** R if you do not have access to the above packages \n\n\n\n## Preparation { }\n\n\n### Load packages {.unnumbered}\n\nThis code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load installed packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npacman::p_load(\n rio, # File import\n here, # File locator\n skimr, # get overview of data\n tidyverse, # data management + ggplot2 graphics \n gtsummary, # summary statistics and tests\n rstatix, # summary statistics and statistical tests\n janitor, # adding totals and percents to tables\n scales, # easily convert proportions to percents \n flextable # converting tables to pretty images\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n There is a binary version available but the source version is later:\n binary source needs_compilation\ncrul 1.4.2 1.5.0 FALSE\n\npackage 'triebeard' successfully unpacked and MD5 sums checked\npackage 'urltools' successfully unpacked and MD5 sums checked\npackage 'httpcode' successfully unpacked and MD5 sums checked\npackage 'fontBitstreamVera' successfully unpacked and MD5 sums checked\npackage 'fontLiberation' successfully unpacked and MD5 sums checked\npackage 'gfonts' successfully unpacked and MD5 sums checked\npackage 'fontquiver' successfully unpacked and MD5 sums checked\npackage 'gdtools' successfully unpacked and MD5 sums checked\npackage 'officer' successfully unpacked and MD5 sums checked\npackage 'flextable' successfully unpacked and MD5 sums checked\n\nThe downloaded binary packages are in\n\tC:\\Users\\ah1114\\AppData\\Local\\Temp\\Rtmp6VfOXs\\downloaded_packages\n```\n\n\n:::\n:::\n\n\n### Import data {.unnumbered}\n\nWe import the dataset of cases from a simulated Ebola epidemic. If you want to follow along, click to download the \"clean\" linelist (as .rds file). Import your data with the `import()` function from the **rio** package (it accepts many file types like .xlsx, .rds, .csv - see the [Import and export](importing.qmd) page for details). \n\n\n::: {.cell}\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: The `trust` argument of `import()` should be explicit for serialization formats\nas of rio 1.0.3.\nℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.\nℹ The deprecated feature was likely used in the rio package.\n Please report the issue at .\n```\n\n\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\n# import the linelist\nlinelist <- import(\"linelist_cleaned.rds\")\n```\n:::\n\n\nThe first 50 rows of the linelist are displayed below.\n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n\n\n\n\n\n## Browse data { }\n\n### **skimr** package {.unnumbered}\n\nBy using the **skimr** package, you can get a detailed and aesthetically pleasing overview of each of the variables in your dataset. Read more about **skimr** at its [github page](https://github.com/ropensci/skimr). \n\nBelow, the function `skim()` is applied to the entire `linelist` data frame. An overview of the data frame and a summary of every column (by class) is produced. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## get information about each variable in a dataset \nskim(linelist)\n```\n:::\n\n::: {.cell}\n::: {.cell-output-display}\n\nTable: Data summary\n\n| | |\n|:------------------------|:--------|\n|Name |linelist |\n|Number of rows |5888 |\n|Number of columns |30 |\n|_______________________ | |\n|Column type frequency: | |\n|character |13 |\n|Date |4 |\n|factor |2 |\n|numeric |11 |\n|________________________ | |\n|Group variables |None |\n\n\n**Variable type: character**\n\n|skim_variable | n_missing| complete_rate| min| max| empty| n_unique| whitespace|\n|:--------------|---------:|-------------:|---:|---:|-----:|--------:|----------:|\n|case_id | 0| 1.00| 6| 6| 0| 5888| 0|\n|outcome | 1323| 0.78| 5| 7| 0| 2| 0|\n|gender | 278| 0.95| 1| 1| 0| 2| 0|\n|age_unit | 0| 1.00| 5| 6| 0| 2| 0|\n|hospital | 0| 1.00| 5| 36| 0| 6| 0|\n|infector | 2088| 0.65| 6| 6| 0| 2697| 0|\n|source | 2088| 0.65| 5| 7| 0| 2| 0|\n|fever | 249| 0.96| 2| 3| 0| 2| 0|\n|chills | 249| 0.96| 2| 3| 0| 2| 0|\n|cough | 249| 0.96| 2| 3| 0| 2| 0|\n|aches | 249| 0.96| 2| 3| 0| 2| 0|\n|vomit | 249| 0.96| 2| 3| 0| 2| 0|\n|time_admission | 765| 0.87| 5| 5| 0| 1072| 0|\n\n\n**Variable type: Date**\n\n|skim_variable | n_missing| complete_rate|min |max |median | n_unique|\n|:--------------------|---------:|-------------:|:----------|:----------|:----------|--------:|\n|date_infection | 2087| 0.65|2014-03-19 |2015-04-27 |2014-10-11 | 359|\n|date_onset | 256| 0.96|2014-04-07 |2015-04-30 |2014-10-23 | 367|\n|date_hospitalisation | 0| 1.00|2014-04-17 |2015-04-30 |2014-10-23 | 363|\n|date_outcome | 936| 0.84|2014-04-19 |2015-06-04 |2014-11-01 | 371|\n\n\n**Variable type: factor**\n\n|skim_variable | n_missing| complete_rate|ordered | n_unique|top_counts |\n|:-------------|---------:|-------------:|:-------|--------:|:-----------------------------------------|\n|age_cat | 86| 0.99|FALSE | 8|0-4: 1095, 5-9: 1095, 20-: 1073, 10-: 941 |\n|age_cat5 | 86| 0.99|FALSE | 17|0-4: 1095, 5-9: 1095, 10-: 941, 15-: 743 |\n\n\n**Variable type: numeric**\n\n|skim_variable | n_missing| complete_rate| mean| sd| p0| p25| p50| p75| p100|\n|:---------------|---------:|-------------:|------:|-----:|--------:|------:|------:|------:|-------:|\n|generation | 0| 1.00| 16.56| 5.79| 0.00| 13.00| 16.00| 20.00| 37.00|\n|age | 86| 0.99| 16.07| 12.62| 0.00| 6.00| 13.00| 23.00| 84.00|\n|age_years | 86| 0.99| 16.02| 12.64| 0.00| 6.00| 13.00| 23.00| 84.00|\n|lon | 0| 1.00| -13.23| 0.02| -13.27| -13.25| -13.23| -13.22| -13.21|\n|lat | 0| 1.00| 8.47| 0.01| 8.45| 8.46| 8.47| 8.48| 8.49|\n|wt_kg | 0| 1.00| 52.64| 18.58| -11.00| 41.00| 54.00| 66.00| 111.00|\n|ht_cm | 0| 1.00| 124.96| 49.52| 4.00| 91.00| 129.00| 159.00| 295.00|\n|ct_blood | 0| 1.00| 21.21| 1.69| 16.00| 20.00| 22.00| 22.00| 26.00|\n|temp | 149| 0.97| 38.56| 0.98| 35.20| 38.20| 38.80| 39.20| 40.80|\n|bmi | 0| 1.00| 46.89| 55.39| -1200.00| 24.56| 32.12| 50.01| 1250.00|\n|days_onset_hosp | 256| 0.96| 2.06| 2.26| 0.00| 1.00| 1.00| 3.00| 22.00|\n\n\n:::\n:::\n\n\n\n\nYou can also use the `summary()` function, from **base** R, to get information about an entire dataset, but this output can be more difficult to read than using **skimr**. Therefore the output is not shown below, to conserve page space. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n## get information about each column in a dataset \nsummary(linelist)\n```\n:::\n\n\n\n### Summary statistics {.unnumbered} \n\nYou can use **base** R functions to return summary statistics on a numeric column. You can return most of the useful summary statistics for a numeric column using `summary()`, as below. Note that the data frame name must also be specified as shown below. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(linelist$age_years)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n Min. 1st Qu. Median Mean 3rd Qu. Max. NA's \n 0.00 6.00 13.00 16.02 23.00 84.00 86 \n```\n\n\n:::\n:::\n\n\nYou can access and save one specific part of it with index brackets [ ]: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(linelist$age_years)[[2]] # return only the 2nd element\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 6\n```\n\n\n:::\n\n```{.r .cell-code}\n# equivalent, alternative to above by element name\n# summary(linelist$age_years)[[\"1st Qu.\"]] \n```\n:::\n\n\nYou can return individual statistics with **base** R functions like `max()`, `min()`, `median()`, `mean()`, `quantile()`, `sd()`, and `range()`. See the [R basics](basics.qmd) page for a complete list. \n\n**_CAUTION:_** If your data contain missing values, R wants you to know this and so will return `NA` unless you specify to the above mathematical functions that you want R to ignore missing values, via the argument `na.rm = TRUE`.\n\n\nYou can use the `get_summary_stats()` function from **rstatix** to return summary statistics *in a data frame format*. This can be helpful for performing subsequent operations or plotting on the numbers. See the [Simple statistical tests](stat_tests.qmd) page for more details on the **rstatix** package and its functions. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n get_summary_stats(\n age, wt_kg, ht_cm, ct_blood, temp, # columns to calculate for\n type = \"common\") # summary stats to return\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 5 × 10\n variable n min max median iqr mean sd se ci\n \n1 age 5802 0 84 13 17 16.1 12.6 0.166 0.325\n2 wt_kg 5888 -11 111 54 25 52.6 18.6 0.242 0.475\n3 ht_cm 5888 4 295 129 68 125. 49.5 0.645 1.26 \n4 ct_blood 5888 16 26 22 2 21.2 1.69 0.022 0.043\n5 temp 5739 35.2 40.8 38.8 1 38.6 0.977 0.013 0.025\n```\n\n\n:::\n:::\n\n\n\n\n\n\n## **janitor** package {#tbl_janitor} \n\nThe **janitor** packages offers the `tabyl()` function to produce tabulations and cross-tabulations, which can be \"adorned\" or modified with helper functions to display percents, proportions, counts, etc. \n\nBelow, we pipe the `linelist` data frame to **janitor** functions and print the result. If desired, you can also save the resulting tables with the assignment operator `<-`. \n\n### Simple tabyl {.unnumbered} \n\nThe default use of `tabyl()` on a specific column produces the unique values, counts, and column-wise \"percents\" (actually proportions). The proportions may have many digits. You can adjust the number of decimals with `adorn_rounding()` as described below. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% tabyl(age_cat)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat n percent valid_percent\n 0-4 1095 0.185971467 0.188728025\n 5-9 1095 0.185971467 0.188728025\n 10-14 941 0.159816576 0.162185453\n 15-19 743 0.126188859 0.128059290\n 20-29 1073 0.182235054 0.184936229\n 30-49 754 0.128057065 0.129955188\n 50-69 95 0.016134511 0.016373664\n 70+ 6 0.001019022 0.001034126\n 86 0.014605978 NA\n```\n\n\n:::\n:::\n\nAs you can see above, if there are missing values they display in a row labeled ``. You can suppress them with `show_na = FALSE`. If there are no missing values, this row will not appear. If there are missing values, all proportions are given as both raw (denominator inclusive of `NA` counts) and \"valid\" (denominator excludes `NA` counts). \n\nIf the column is class Factor and only certain levels are present in your data, all levels will still appear in the table. You can suppress this feature by specifying `show_missing_levels = FALSE`. Read more on the [Factors](factors.qmd) page. \n\n### Cross-tabulation {.unnumbered} \n\nCross-tabulation counts are achieved by adding one or more additional columns within `tabyl()`. Note that now only counts are returned - proportions and percents can be added with additional steps shown below. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% tabyl(age_cat, gender)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat f m NA_\n 0-4 640 416 39\n 5-9 641 412 42\n 10-14 518 383 40\n 15-19 359 364 20\n 20-29 468 575 30\n 30-49 179 557 18\n 50-69 2 91 2\n 70+ 0 5 1\n 0 0 86\n```\n\n\n:::\n:::\n\n\n### \"Adorning\" the tabyl {#tbl_adorn .unnumbered} \n\nUse **janitor**'s \"adorn\" functions to add totals or convert to proportions, percents, or otherwise adjust the display. Often, you will pipe the tabyl through several of these functions. \n\n\nFunction | Outcome \n-------------------|--------------------------------\n`adorn_totals()` | Adds totals (`where = ` \"row\", \"col\", or \"both\"). Set `name =` for \"Total\". \n`adorn_percentages()` | Convert counts to proportions, with `denominator = ` \"row\", \"col\", or \"all\" \n`adorn_pct_formatting()` | Converts proportions to percents. Specify `digits =`. Remove the \"%\" symbol with `affix_sign = FALSE`. \n`adorn_rounding()` | To round proportions to `digits =` places. To round percents use `adorn_pct_formatting()` with `digits = `. \n`adorn_ns()` | Add counts to a table of proportions or percents. Indicate `position =` \"rear\" to show counts in parentheses, or \"front\" to put the percents in parentheses. \n`adorn_title()` | Add string via arguments `row_name = ` and/or `col_name = ` \n\nBe conscious of the order you apply the above functions. Below are some examples. \n\nA simple one-way table with percents instead of the default proportions. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% # case linelist\n tabyl(age_cat) %>% # tabulate counts and proportions by age category\n adorn_pct_formatting() # convert proportions to percents\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat n percent valid_percent\n 0-4 1095 18.6% 18.9%\n 5-9 1095 18.6% 18.9%\n 10-14 941 16.0% 16.2%\n 15-19 743 12.6% 12.8%\n 20-29 1073 18.2% 18.5%\n 30-49 754 12.8% 13.0%\n 50-69 95 1.6% 1.6%\n 70+ 6 0.1% 0.1%\n 86 1.5% -\n```\n\n\n:::\n:::\n\n\nA cross-tabulation with a total row and row percents. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n tabyl(age_cat, gender) %>% # counts by age and gender\n adorn_totals(where = \"row\") %>% # add total row\n adorn_percentages(denominator = \"row\") %>% # convert counts to proportions\n adorn_pct_formatting(digits = 1) # convert proportions to percents\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat f m NA_\n 0-4 58.4% 38.0% 3.6%\n 5-9 58.5% 37.6% 3.8%\n 10-14 55.0% 40.7% 4.3%\n 15-19 48.3% 49.0% 2.7%\n 20-29 43.6% 53.6% 2.8%\n 30-49 23.7% 73.9% 2.4%\n 50-69 2.1% 95.8% 2.1%\n 70+ 0.0% 83.3% 16.7%\n 0.0% 0.0% 100.0%\n Total 47.7% 47.6% 4.7%\n```\n\n\n:::\n:::\n\n\nA cross-tabulation adjusted so that both counts and percents are displayed. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% # case linelist\n tabyl(age_cat, gender) %>% # cross-tabulate counts\n adorn_totals(where = \"row\") %>% # add a total row\n adorn_percentages(denominator = \"col\") %>% # convert to proportions\n adorn_pct_formatting() %>% # convert to percents\n adorn_ns(position = \"front\") %>% # display as: \"count (percent)\"\n adorn_title( # adjust titles\n row_name = \"Age Category\",\n col_name = \"Gender\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n Gender \n Age Category f m NA_\n 0-4 640 (22.8%) 416 (14.8%) 39 (14.0%)\n 5-9 641 (22.8%) 412 (14.7%) 42 (15.1%)\n 10-14 518 (18.5%) 383 (13.7%) 40 (14.4%)\n 15-19 359 (12.8%) 364 (13.0%) 20 (7.2%)\n 20-29 468 (16.7%) 575 (20.5%) 30 (10.8%)\n 30-49 179 (6.4%) 557 (19.9%) 18 (6.5%)\n 50-69 2 (0.1%) 91 (3.2%) 2 (0.7%)\n 70+ 0 (0.0%) 5 (0.2%) 1 (0.4%)\n 0 (0.0%) 0 (0.0%) 86 (30.9%)\n Total 2,807 (100.0%) 2,803 (100.0%) 278 (100.0%)\n```\n\n\n:::\n:::\n\n\n\n\n### Printing the tabyl {.unnumbered}\n\nBy default, the tabyl will print raw to your R console. \n\nAlternatively, you can pass the tabyl to **flextable** or similar package to print as a \"pretty\" image in the RStudio Viewer, which could be exported as .png, .jpeg, .html, etc. This is discussed in the page [Tables for presentation](tables_presentation.qmd). Note that if printing in this manner and using `adorn_titles()`, you must specify `placement = \"combined\"`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>%\n tabyl(age_cat, gender) %>% \n adorn_totals(where = \"col\") %>% \n adorn_percentages(denominator = \"col\") %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\") %>% \n adorn_title(\n row_name = \"Age Category\",\n col_name = \"Gender\",\n placement = \"combined\") %>% # this is necessary to print as image\n flextable::flextable() %>% # convert to pretty image\n flextable::autofit() # format to one line per row \n```\n\n::: {.cell-output-display}\n\n```{=html}\n

    Age Category/Gender

    f

    m

    NA_

    Total

    0-4

    640 (22.8%)

    416 (14.8%)

    39 (14.0%)

    1,095 (18.6%)

    5-9

    641 (22.8%)

    412 (14.7%)

    42 (15.1%)

    1,095 (18.6%)

    10-14

    518 (18.5%)

    383 (13.7%)

    40 (14.4%)

    941 (16.0%)

    15-19

    359 (12.8%)

    364 (13.0%)

    20 (7.2%)

    743 (12.6%)

    20-29

    468 (16.7%)

    575 (20.5%)

    30 (10.8%)

    1,073 (18.2%)

    30-49

    179 (6.4%)

    557 (19.9%)

    18 (6.5%)

    754 (12.8%)

    50-69

    2 (0.1%)

    91 (3.2%)

    2 (0.7%)

    95 (1.6%)

    70+

    0 (0.0%)

    5 (0.2%)

    1 (0.4%)

    6 (0.1%)

    0 (0.0%)

    0 (0.0%)

    86 (30.9%)

    86 (1.5%)

    \n```\n\n:::\n:::\n\n\n\n### Use on other tables {.unnumbered} \n\nYou can use **janitor**'s `adorn_*()` functions on other tables, such as those created by `summarise()` and `count()` from **dplyr**, or `table()` from **base** R. Simply pipe the table to the desired **janitor** function. For example: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n count(hospital) %>% # dplyr function\n adorn_totals() # janitor function\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n hospital n\n Central Hospital 454\n Military Hospital 896\n Missing 1469\n Other 885\n Port Hospital 1762\n St. Mark's Maternity Hospital (SMMH) 422\n Total 5888\n```\n\n\n:::\n:::\n\n\n\n### Saving the tabyl {.unnumbered} \n\nIf you convert the table to a \"pretty\" image with a package like **flextable**, you can save it with functions from that package - like `save_as_html()`, `save_as_word()`, `save_as_ppt()`, and `save_as_image()` from **flextable** (as discussed more extensively in the [Tables for presentation](tables_presentation.qmd) page). Below, the table is saved as a Word document, in which it can be further hand-edited. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>%\n tabyl(age_cat, gender) %>% \n adorn_totals(where = \"col\") %>% \n adorn_percentages(denominator = \"col\") %>% \n adorn_pct_formatting() %>% \n adorn_ns(position = \"front\") %>% \n adorn_title(\n row_name = \"Age Category\",\n col_name = \"Gender\",\n placement = \"combined\") %>% \n flextable::flextable() %>% # convert to image\n flextable::autofit() %>% # ensure only one line per row\n flextable::save_as_docx(path = \"tabyl.docx\") # save as Word document to filepath\n```\n:::\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n![](../images/tabyl_word.png){fig-align='center' width=50%}\n:::\n:::\n\n\n### Statistics {#janitor_age_out_stats .unnumbered} \n\nYou can apply statistical tests on tabyls, like `chisq.test()` or `fisher.test()` from the **stats** package, as shown below. Note missing values are not allowed so they are excluded from the tabyl with `show_na = FALSE`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_by_outcome <- linelist %>% \n tabyl(age_cat, outcome, show_na = FALSE) \n\nchisq.test(age_by_outcome)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n\tPearson's Chi-squared test\n\ndata: age_by_outcome\nX-squared = 6.4931, df = 7, p-value = 0.4835\n```\n\n\n:::\n:::\n\n\nSee the page on [Simple statistical tests](stat_tests.qmd) for more code and tips about statistics. \n\n### Other tips {.unnumbered} \n\n* Include the argument `na.rm = TRUE` to exclude missing values from any of the above calculations. \n* If applying any `adorn_*()` helper functions to tables not created by `tabyl()`, you can specify particular column(s) to apply them to like `adorn_percentage(,,,c(cases,deaths))` (specify them to the 4th unnamed argument). The syntax is not simple. Consider using `summarise()` instead. \n* You can read more detail in the [janitor page](https://cran.r-project.org/web/packages/janitor/vignettes/janitor.html) and this [tabyl vignette](https://cran.r-project.org/web/packages/janitor/vignettes/tabyls.html). \n\n\n\n\n## **dplyr** package \n\n**dplyr** is part of the **tidyverse** packages and is an very common data management tool. Creating tables with **dplyr** functions `summarise()` and `count()` is a useful approach to calculating summary statistics, summarize *by group*, or pass tables to `ggplot()`. \n\n`summarise()` creates a *new, summary data frame*. If the data are *ungrouped*, it will return a one-row dataframe with the specified summary statistics of the entire data frame. If the data are *grouped*, the new data frame will have one row per *group* (see [Grouping data](grouping.qmd) page). \n\nWithin the `summarise()` parentheses, you provide the names of each new summary column followed by an equals sign and a statistical function to apply. \n\n**_TIP:_** The summarise function works with both UK and US spelling (`summarise()` and `summarize()`).\n\n### Get counts {.unnumbered} \n\nThe most simple function to apply within `summarise()` is `n()`. Leave the parentheses empty to count the number of rows. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% # begin with linelist\n summarise(n_rows = n()) # return new summary dataframe with column n_rows\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n n_rows\n1 5888\n```\n\n\n:::\n:::\n\n\nThis gets more interesting if we have grouped the data beforehand. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(age_cat) %>% # group data by unique values in column age_cat\n summarise(n_rows = n()) # return number of rows *per group*\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 9 × 2\n age_cat n_rows\n \n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 86\n```\n\n\n:::\n:::\n\n\nThe above command can be shortened by using the `count()` function instead. `count()` does the following: \n\n1) Groups the data by the columns provided to it \n2) Summarises them with `n()` (creating column `n`) \n3) Un-groups the data \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n count(age_cat)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat n\n1 0-4 1095\n2 5-9 1095\n3 10-14 941\n4 15-19 743\n5 20-29 1073\n6 30-49 754\n7 50-69 95\n8 70+ 6\n9 86\n```\n\n\n:::\n:::\n\n\nYou can change the name of the counts column from the default `n` to something else by specifying it to `name = `. \n\nTabulating counts of two or more grouping columns are still returned in “long” format, with the counts in the `n` column. See the page on [Pivoting data](pivoting.qmd) to learn about \"long\" and \"wide\" data formats. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n count(age_cat, outcome)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat outcome n\n1 0-4 Death 471\n2 0-4 Recover 364\n3 0-4 260\n4 5-9 Death 476\n5 5-9 Recover 391\n6 5-9 228\n7 10-14 Death 438\n8 10-14 Recover 303\n9 10-14 200\n10 15-19 Death 323\n11 15-19 Recover 251\n12 15-19 169\n13 20-29 Death 477\n14 20-29 Recover 367\n15 20-29 229\n16 30-49 Death 329\n17 30-49 Recover 238\n18 30-49 187\n19 50-69 Death 33\n20 50-69 Recover 38\n21 50-69 24\n22 70+ Death 3\n23 70+ Recover 3\n24 Death 32\n25 Recover 28\n26 26\n```\n\n\n:::\n:::\n\n\n\n### Show all levels {.unnumbered} \n\nIf you are tabling a column of class *factor* you can ensure that *all* levels are shown (not just the levels with values in the data) by adding `.drop = FALSE` into the `summarise()` or `count()` command. \n\nThis technique is useful to standardise your tables/plots. For example if you are creating figures for multiple sub-groups, or repeatedly creating the figure for routine reports. In each of these circumstances, the presence of values in the data may fluctuate, but you can define levels that remain constant. \n\nSee the page on [Factors](factors.qmd) for more information. \n\n\n\n\n### Proportions {#tbl_dplyr_prop .unnumbered} \n\nProportions can be added by piping the table to `mutate()` to create a new column. Define the new column as the counts column (`n` by default) divided by the `sum()` of the counts column (this will return a proportion). \n\nNote that in this case, `sum()` in the `mutate()` command will return the sum of the whole column `n` for use as the proportion denominator. As explained [in the Grouping data page](grouping.qmd#group_summarise), *if* `sum()` is used in *grouped* data (e.g. if the `mutate()` immediately followed a `group_by()` command), it will return sums *by group*. As stated just above, `count()` finishes its actions by *ungrouping*. Thus, in this scenario we get full column proportions. \n\nTo easily display percents, you can wrap the proportion in the function `percent()` from the package **scales** (note this convert to class character). \n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_summary <- linelist %>% \n count(age_cat) %>% # group and count by gender (produces \"n\" column)\n mutate( # create percent of column - note the denominator\n percent = scales::percent(n / sum(n))) \n\n# print\nage_summary\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_cat n percent\n1 0-4 1095 18.60%\n2 5-9 1095 18.60%\n3 10-14 941 15.98%\n4 15-19 743 12.62%\n5 20-29 1073 18.22%\n6 30-49 754 12.81%\n7 50-69 95 1.61%\n8 70+ 6 0.10%\n9 86 1.46%\n```\n\n\n:::\n:::\n\n\nBelow is a method to calculate proportions *within groups*. It relies on different levels of data grouping being selectively applied and removed. First, the data are grouped on `outcome` via `group_by()`. Then, `count()` is applied. This function further groups the data by `age_cat` and returns counts for each `outcome`-`age-cat` combination. Importantly - as it finishes its process, `count()` also *ungroups* the `age_cat` grouping, so the only remaining data grouping is the original grouping by `outcome`. Thus, the final step of calculating proportions (denominator `sum(n)`) is still grouped by `outcome`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_by_outcome <- linelist %>% # begin with linelist\n group_by(outcome) %>% # group by outcome \n count(age_cat) %>% # group and count by age_cat, and then remove age_cat grouping\n mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group\n```\n:::\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\n\n\n\n### Plotting {.unnumbered} \n\nTo display a \"long\" table output like the above with `ggplot()` is relatively straight-forward. The data are naturally in \"long\" format, which is naturally accepted by `ggplot()`. See further examples in the pages [ggplot basics](ggplot_basics.qmd) and [ggplot tips](ggplot_tips.qmd). \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% # begin with linelist\n count(age_cat, outcome) %>% # group and tabulate counts by two columns\n ggplot()+ # pass new data frame to ggplot\n geom_col( # create bar plot\n mapping = aes( \n x = outcome, # map outcome to x-axis\n fill = age_cat, # map age_cat to the fill\n y = n)) # map the counts column `n` to the height\n```\n\n::: {.cell-output-display}\n![](tables_descriptive_files/figure-html/unnamed-chunk-29-1.png){width=672}\n:::\n:::\n\n\n\n### Summary statistics {.unnumbered} \n\nOne major advantage of **dplyr** and `summarise()` is the ability to return more advanced statistical summaries like `median()`, `mean()`, `max()`, `min()`, `sd()` (standard deviation), and percentiles. You can also use `sum()` to return the number of rows that meet certain logical criteria. As above, these outputs can be produced for the whole data frame set, or by group. \n\nThe syntax is the same - within the `summarise()` parentheses you provide the names of each new summary column followed by an equals sign and a statistical function to apply. Within the statistical function, give the column(s) to be operated on and any relevant arguments (e.g. `na.rm = TRUE` for most mathematical functions). \n\nYou can also use `sum()` to return the number of rows that meet a logical criteria. The expression within is counted if it evaluates to `TRUE`. For example: \n\n* `sum(age_years < 18, na.rm=T)` \n* `sum(gender == \"male\", na.rm=T)` \n* `sum(response %in% c(\"Likely\", \"Very Likely\"))` \n\nBelow, `linelist` data are summarised to describe the days delay from symptom onset to hospital admission (column `days_onset_hosp`), by hospital. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary_table <- linelist %>% # begin with linelist, save out as new object\n group_by(hospital) %>% # group all calculations by hospital\n summarise( # only the below summary columns will be returned\n cases = n(), # number of rows per group\n delay_max = max(days_onset_hosp, na.rm = T), # max delay\n delay_mean = round(mean(days_onset_hosp, na.rm=T), digits = 1), # mean delay, rounded\n delay_sd = round(sd(days_onset_hosp, na.rm = T), digits = 1), # standard deviation of delays, rounded\n delay_3 = sum(days_onset_hosp >= 3, na.rm = T), # number of rows with delay of 3 or more days\n pct_delay_3 = scales::percent(delay_3 / cases) # convert previously-defined delay column to percent \n )\n\nsummary_table # print\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 7\n hospital cases delay_max delay_mean delay_sd delay_3 pct_delay_3\n \n1 Central Hospital 454 12 1.9 1.9 108 24% \n2 Military Hospital 896 15 2.1 2.4 253 28% \n3 Missing 1469 22 2.1 2.3 399 27% \n4 Other 885 18 2 2.2 234 26% \n5 Port Hospital 1762 16 2.1 2.2 470 27% \n6 St. Mark's Maternity … 422 18 2.1 2.3 116 27% \n```\n\n\n:::\n:::\n\n\n\nSome tips: \n\n* Use `sum()` with a logic statement to \"count\" rows that meet certain criteria (`==`) \n* Note the use of `na.rm = TRUE` within mathematical functions like `sum()`, otherwise `NA` will be returned if there are any missing values \n* Use the function `percent()` from the **scales** package to easily convert to percents \n * Set `accuracy = ` to 0.1 or 0.01 to ensure 1 or 2 decimal places respectively \n* Use `round()` from **base** R to specify decimals \n* To calculate these statistics on the entire dataset, use `summarise()` without `group_by()` \n* You may create columns for the purposes of later calculations (e.g. denominators) that you eventually drop from your data frame with `select()`. \n\n\n### Conditional statistics {.unnumbered} \n\nYou may want to return *conditional statistics* - e.g. the maximum of rows that meet certain criteria. This can be done by subsetting the column with brackets `[ ]`. The example below returns the maximum temperature for patients classified having or not having fever. Be aware however - it may be more appropriate to add another column to the `group_by()` command and `pivot_wider()` (as demonstrated [below](#tbls_pivot_wider)). \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n max_temp_fvr = max(temp[fever == \"yes\"], na.rm = T),\n max_temp_no = max(temp[fever == \"no\"], na.rm = T)\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 3\n hospital max_temp_fvr max_temp_no\n \n1 Central Hospital 40.4 38 \n2 Military Hospital 40.5 38 \n3 Missing 40.6 38 \n4 Other 40.8 37.9\n5 Port Hospital 40.6 38 \n6 St. Mark's Maternity Hospital (SMMH) 40.6 37.9\n```\n\n\n:::\n:::\n\n\n\n\n### Glueing together {.unnumbered} \n\nThe function `str_glue()` from **stringr** is useful to combine values from several columns into one new column. In this context this is typically used *after* the `summarise()` command. \n\nIn the [Characters and strings](characters_strings.qmd) page, various options for combining columns are discussed, including `unite()`, and `paste0()`. In this use case, we advocate for `str_glue()` because it is more flexible than `unite()` and has more simple syntax than `paste0()`. \n\nBelow, the `summary_table` data frame (created above) is mutated such that columns `delay_mean` and `delay_sd` are combined, parentheses formating is added to the new column, and their respective old columns are removed. \n\nThen, to make the table more presentable, a total row is added with `adorn_totals()` from **janitor** (which ignores non-numeric columns). Lastly, we use `select()` from **dplyr** to both re-order and rename to nicer column names. \n\nNow you could pass to **flextable** and print the table to Word, .png, .jpeg, .html, Powerpoint, RMarkdown, etc.! (see the [Tables for presentation](tables_presentation.qmd) page). \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary_table %>% \n mutate(delay = str_glue(\"{delay_mean} ({delay_sd})\")) %>% # combine and format other values\n select(-c(delay_mean, delay_sd)) %>% # remove two old columns \n adorn_totals(where = \"row\") %>% # add total row\n select( # order and rename cols\n \"Hospital Name\" = hospital,\n \"Cases\" = cases,\n \"Max delay\" = delay_max,\n \"Mean (sd)\" = delay,\n \"Delay 3+ days\" = delay_3,\n \"% delay 3+ days\" = pct_delay_3\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n Hospital Name Cases Max delay Mean (sd) Delay 3+ days\n Central Hospital 454 12 1.9 (1.9) 108\n Military Hospital 896 15 2.1 (2.4) 253\n Missing 1469 22 2.1 (2.3) 399\n Other 885 18 2 (2.2) 234\n Port Hospital 1762 16 2.1 (2.2) 470\n St. Mark's Maternity Hospital (SMMH) 422 18 2.1 (2.3) 116\n Total 5888 101 - 1580\n % delay 3+ days\n 24%\n 28%\n 27%\n 26%\n 27%\n 27%\n -\n```\n\n\n:::\n:::\n\n\n#### Percentiles {.unnumbered} \n\n*Percentiles* and quantiles in **dplyr** deserve a special mention. To return quantiles, use `quantile()` with the defaults or specify the value(s) you would like with `probs = `.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# get default percentile values of age (0%, 25%, 50%, 75%, 100%)\nlinelist %>% \n summarise(age_percentiles = quantile(age_years, na.rm = TRUE))\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: Returning more (or less) than 1 row per `summarise()` group was deprecated in\ndplyr 1.1.0.\nℹ Please use `reframe()` instead.\nℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`\n always returns an ungrouped data frame and adjust accordingly.\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_percentiles\n1 0\n2 6\n3 13\n4 23\n5 84\n```\n\n\n:::\n\n```{.r .cell-code}\n# get manually-specified percentile values of age (5%, 50%, 75%, 98%)\nlinelist %>% \n summarise(\n age_percentiles = quantile(\n age_years,\n probs = c(.05, 0.5, 0.75, 0.98), \n na.rm=TRUE)\n )\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: Returning more (or less) than 1 row per `summarise()` group was deprecated in\ndplyr 1.1.0.\nℹ Please use `reframe()` instead.\nℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`\n always returns an ungrouped data frame and adjust accordingly.\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n age_percentiles\n1 1\n2 13\n3 23\n4 48\n```\n\n\n:::\n:::\n\n\nIf you want to return quantiles *by group*, you may encounter long and less useful outputs if you simply add another column to `group_by()`. So, try this approach instead - create a column for each quantile level desired. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n# get manually-specified percentile values of age (5%, 50%, 75%, 98%)\nlinelist %>% \n group_by(hospital) %>% \n summarise(\n p05 = quantile(age_years, probs = 0.05, na.rm=T),\n p50 = quantile(age_years, probs = 0.5, na.rm=T),\n p75 = quantile(age_years, probs = 0.75, na.rm=T),\n p98 = quantile(age_years, probs = 0.98, na.rm=T)\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 5\n hospital p05 p50 p75 p98\n \n1 Central Hospital 1 12 21 48 \n2 Military Hospital 1 13 24 45 \n3 Missing 1 13 23 48.2\n4 Other 1 13 23 50 \n5 Port Hospital 1 14 24 49 \n6 St. Mark's Maternity Hospital (SMMH) 2 12 22 50.2\n```\n\n\n:::\n:::\n\n\nWhile **dplyr** `summarise()` certainly offers more fine control, you may find that all the summary statistics you need can be produced with `get_summary_stat()` from the **rstatix** package. If operating on grouped data, if will return 0%, 25%, 50%, 75%, and 100%. If applied to ungrouped data, you can specify the percentiles with `probs = c(.05, .5, .75, .98)`. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(hospital) %>% \n rstatix::get_summary_stats(age, type = \"quantile\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 8\n hospital variable n `0%` `25%` `50%` `75%` `100%`\n \n1 Central Hospital age 445 0 6 12 21 58\n2 Military Hospital age 884 0 6 14 24 72\n3 Missing age 1441 0 6 13 23 76\n4 Other age 873 0 6 13 23 69\n5 Port Hospital age 1739 0 6 14 24 68\n6 St. Mark's Maternity Hospital (… age 420 0 7 12 22 84\n```\n\n\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n rstatix::get_summary_stats(age, type = \"quantile\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 1 × 7\n variable n `0%` `25%` `50%` `75%` `100%`\n \n1 age 5802 0 6 13 23 84\n```\n\n\n:::\n:::\n\n\n\n\n### Summarise aggregated data {.unnumbered} \n\n*If you begin with aggregated data*, using `n()` return the number of *rows*, not the sum of the aggregated counts. To get sums, use `sum()` on the data's counts column. \n\nFor example, let's say you are beginning with the data frame of counts below, called `linelist_agg` - it shows in \"long\" format the case counts by outcome and gender. \n\nBelow we create this example data frame of `linelist` case counts by outcome and gender (missing values removed for clarity). \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist_agg <- linelist %>% \n drop_na(gender, outcome) %>% \n count(outcome, gender)\n\nlinelist_agg\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n outcome gender n\n1 Death f 1227\n2 Death m 1228\n3 Recover f 953\n4 Recover m 950\n```\n\n\n:::\n:::\n\n\nTo sum the counts (in column `n`) by group you can use `summarise()` but set the new column equal to `sum(n, na.rm=T)`. To add a conditional element to the sum operation, you can use the subset bracket [ ] syntax on the counts column. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist_agg %>% \n group_by(outcome) %>% \n summarise(\n total_cases = sum(n, na.rm=T),\n male_cases = sum(n[gender == \"m\"], na.rm=T),\n female_cases = sum(n[gender == \"f\"], na.rm=T))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 4\n outcome total_cases male_cases female_cases\n \n1 Death 2455 1228 1227\n2 Recover 1903 950 953\n```\n\n\n:::\n:::\n\n\n\n\n\n### `across()` multiple columns {.unnumbered} \n\nYou can use `summarise()` across multiple columns using `across()`. This makes life easier when you want to calculate the same statistics for many columns. Place `across()` within `summarise()` and specify the following: \n\n* `.cols = ` as either a vector of column names `c()` or \"tidyselect\" helper functions (explained below) \n* `.fns = ` the function to perform (no parentheses) - you can provide multiple within a `list()`\n\nBelow, `mean()` is applied to several numeric columns. A vector of columns are named explicitly to `.cols = ` and a single function `mean` is specified (no parentheses) to `.fns = `. Any additional arguments for the function (e.g. `na.rm=TRUE`) are provided after `.fns = `, separated by a comma. \n\nIt can be difficult to get the order of parentheses and commas correct when using `across()`. Remember that within `across()` you must include the columns, the functions, and any extra arguments needed for the functions. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns\n .fns = mean, # function\n na.rm=T)) # extra arguments\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\nWarning: There was 1 warning in `summarise()`.\nℹ In argument: `across(...)`.\nℹ In group 1: `outcome = \"Death\"`.\nCaused by warning:\n! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.\nSupply arguments directly to `.fns` through an anonymous function instead.\n\n # Previously\n across(a:b, mean, na.rm = TRUE)\n\n # Now\n across(a:b, \\(x) mean(x, na.rm = TRUE))\n```\n\n\n:::\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 3 × 5\n outcome age_years temp wt_kg ht_cm\n \n1 Death 15.9 38.6 52.6 125.\n2 Recover 16.1 38.6 52.5 125.\n3 16.2 38.6 53.0 125.\n```\n\n\n:::\n:::\n\n\nMultiple functions can be run at once. Below the functions `mean` and `sd` are provided to `.fns = ` within a `list()`. You have the opportunity to provide character names (e.g. \"mean\" and \"sd\") which are appended in the new column names. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(.cols = c(age_years, temp, wt_kg, ht_cm), # columns\n .fns = list(\"mean\" = mean, \"sd\" = sd), # multiple functions \n na.rm=T)) # extra arguments\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 3 × 9\n outcome age_years_mean age_years_sd temp_mean temp_sd wt_kg_mean wt_kg_sd\n \n1 Death 15.9 12.3 38.6 0.962 52.6 18.4\n2 Recover 16.1 13.0 38.6 0.997 52.5 18.6\n3 16.2 12.8 38.6 0.976 53.0 18.9\n# ℹ 2 more variables: ht_cm_mean , ht_cm_sd \n```\n\n\n:::\n:::\n\n\nHere are those \"tidyselect\" helper functions you can provide to `.cols = ` to select columns: \n\n* `everything()` - all other columns not mentioned \n* `last_col()` - the last column \n* `where()` - applies a function to all columns and selects those which are TRUE \n* `starts_with()` - matches to a specified prefix. Example: `starts_with(\"date\")`\n* `ends_with()` - matches to a specified suffix. Example: `ends_with(\"_end\")` \n* `contains()` - columns containing a character string. Example: `contains(\"time\")` \n* `matches()` - to apply a regular expression (regex). Example: `contains(\"[pt]al\")` \n* `num_range()` - \n* `any_of()` - matches if column is named. Useful if the name might not exist. Example: `any_of(date_onset, date_death, cardiac_arrest)` \n\n\nFor example, to return the mean of every numeric column use `where()` and provide the function `as.numeric()` (without parentheses). All this remains within the `across()` command. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(outcome) %>% \n summarise(across(\n .cols = where(is.numeric), # all numeric columns in the data frame\n .fns = mean,\n na.rm=T))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 3 × 12\n outcome generation age age_years lon lat wt_kg ht_cm ct_blood temp\n \n1 Death 16.7 15.9 15.9 -13.2 8.47 52.6 125. 21.3 38.6\n2 Recover 16.4 16.2 16.1 -13.2 8.47 52.5 125. 21.1 38.6\n3 16.5 16.3 16.2 -13.2 8.47 53.0 125. 21.2 38.6\n# ℹ 2 more variables: bmi , days_onset_hosp \n```\n\n\n:::\n:::\n\n\n\n### Pivot wider {#tbls_pivot_wider .unnumbered}\n\nIf you prefer your table in \"wide\" format you can transform it using the **tidyr** `pivot_wider()` function. You will likely need to re-name the columns with `rename()`. For more information see the page on [Pivoting data](pivoting.qmd). \n\nThe example below begins with the \"long\" table `age_by_outcome` from the [proportions section](#tbl_dplyr_prop). We create it again and print, for clarity: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_by_outcome <- linelist %>% # begin with linelist\n group_by(outcome) %>% # group by outcome \n count(age_cat) %>% # group and count by age_cat, and then remove age_cat grouping\n mutate(percent = scales::percent(n / sum(n))) # calculate percent - note the denominator is by outcome group\n```\n:::\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\nTo pivot wider, we create the new columns from the *values* in the existing column `age_cat` (by setting `names_from = age_cat`). We also specify that the new table values will come from the existing column `n`, with `values_from = n`. The columns not mentioned in our pivoting command (`outcome`) will remain unchanged on the far left side. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_by_outcome %>% \n select(-percent) %>% # keep only counts for simplicity\n pivot_wider(names_from = age_cat, values_from = n) \n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 3 × 10\n# Groups: outcome [3]\n outcome `0-4` `5-9` `10-14` `15-19` `20-29` `30-49` `50-69` `70+` `NA`\n \n1 Death 471 476 438 323 477 329 33 3 32\n2 Recover 364 391 303 251 367 238 38 3 28\n3 260 228 200 169 229 187 24 NA 26\n```\n\n\n:::\n:::\n\n\n\n### Total rows {#tbl_dplyr_totals .unnumbered} \n\nWhen `summarise()` operates on grouped data it does not automatically produce \"total\" statistics. Below, two approaches to adding a total row are presented: \n\n#### **janitor**'s `adorn_totals()` {.unnumbered} \n\nIf your table consists only of counts or proportions/percents that can be summed into a total, then you can add *sum* totals using **janitor**'s `adorn_totals()` as described in the section above. Note that this function can only sum the numeric columns - if you want to calculate other total summary statistics see the next approach with **dplyr**. \n\nBelow, `linelist` is grouped by gender and summarised into a table that described the number of cases with known outcome, deaths, and recovered. Piping the table to `adorn_totals()` adds a total row at the bottom reflecting the sum of each column. The further `adorn_*()` functions adjust the display as noted in the code. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n group_by(gender) %>%\n summarise(\n known_outcome = sum(!is.na(outcome)), # Number of rows in group where outcome is not missing\n n_death = sum(outcome == \"Death\", na.rm=T), # Number of rows in group where outcome is Death\n n_recover = sum(outcome == \"Recover\", na.rm=T), # Number of rows in group where outcome is Recovered\n ) %>% \n adorn_totals() %>% # Adorn total row (sums of each numeric column)\n adorn_percentages(\"col\") %>% # Get column proportions\n adorn_pct_formatting() %>% # Convert proportions to percents\n adorn_ns(position = \"front\") # display % and counts (with counts in front)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n gender known_outcome n_death n_recover\n f 2,180 (47.8%) 1,227 (47.5%) 953 (48.1%)\n m 2,178 (47.7%) 1,228 (47.6%) 950 (47.9%)\n 207 (4.5%) 127 (4.9%) 80 (4.0%)\n Total 4,565 (100.0%) 2,582 (100.0%) 1,983 (100.0%)\n```\n\n\n:::\n:::\n\n\n#### `summarise()` on \"total\" data and then `bind_rows()` {.unnumbered} \n\nIf your table consists of summary statistics such as `median()`, `mean()`, etc, the `adorn_totals()` approach shown above will *not* be sufficient. Instead, to get summary statistics for the entire dataset you must calculate them with a separate `summarise()` command and then bind the results to the original grouped summary table. To do the binding you can use `bind_rows()` from **dplyr** s described in the [Joining data](joining_matching.qmd) page. Below is an example: \n\nYou can make a summary table of outcome *by hospital* with `group_by()` and `summarise()` like this: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nby_hospital <- linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>% # Remove cases with missing outcome or hospital\n group_by(hospital, outcome) %>% # Group data\n summarise( # Create new summary columns of indicators of interest\n N = n(), # Number of rows per hospital-outcome group \n ct_value = median(ct_blood, na.rm=T)) # median CT value per group\n \nby_hospital # print table\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 10 × 4\n# Groups: hospital [5]\n hospital outcome N ct_value\n \n 1 Central Hospital Death 193 22\n 2 Central Hospital Recover 165 22\n 3 Military Hospital Death 399 21\n 4 Military Hospital Recover 309 22\n 5 Other Death 395 22\n 6 Other Recover 290 21\n 7 Port Hospital Death 785 22\n 8 Port Hospital Recover 579 21\n 9 St. Mark's Maternity Hospital (SMMH) Death 199 22\n10 St. Mark's Maternity Hospital (SMMH) Recover 126 22\n```\n\n\n:::\n:::\n\n\nTo get the totals, run the same `summarise()` command but only group the data by outcome (not by hospital), like this: \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntotals <- linelist %>% \n filter(!is.na(outcome) & hospital != \"Missing\") %>%\n group_by(outcome) %>% # Grouped only by outcome, not by hospital \n summarise(\n N = n(), # These statistics are now by outcome only \n ct_value = median(ct_blood, na.rm=T))\n\ntotals # print table\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 3\n outcome N ct_value\n \n1 Death 1971 22\n2 Recover 1469 22\n```\n\n\n:::\n:::\n\n\nWe can bind these two data frames together. Note that `by_hospital` has 4 columns whereas `totals` has 3 columns. By using `bind_rows()`, the columns are combined by name, and any extra space is filled in with `NA` (e.g the column `hospital` values for the two new `totals` rows). After binding the rows, we convert these empty spaces to \"Total\" using `replace_na()` (see [Cleaning data and core functions](cleaning.qmd) page). \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntable_long <- bind_rows(by_hospital, totals) %>% \n mutate(hospital = replace_na(hospital, \"Total\"))\n```\n:::\n\n\nHere is the new table with \"Total\" rows at the bottom. \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n```\n\n:::\n:::\n\n\nThis table is in a \"long\" format, which may be what you want. *Optionally*, you can *pivot* this table *wider* to make it more readable. See the section on pivoting wider above, and the [Pivoting data](pivoting.qmd) page. You can also add more columns, and arrange it nicely. This code is below. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntable_long %>% \n \n # Pivot wider and format\n ########################\n mutate(hospital = replace_na(hospital, \"Total\")) %>% \n pivot_wider( # Pivot from long to wide\n values_from = c(ct_value, N), # new values are from ct and count columns\n names_from = outcome) %>% # new column names are from outcomes\n mutate( # Add new columns\n N_Known = N_Death + N_Recover, # number with known outcome\n Pct_Death = scales::percent(N_Death / N_Known, 0.1), # percent cases who died (to 1 decimal)\n Pct_Recover = scales::percent(N_Recover / N_Known, 0.1)) %>% # percent who recovered (to 1 decimal)\n select( # Re-order columns\n hospital, N_Known, # Intro columns\n N_Recover, Pct_Recover, ct_value_Recover, # Recovered columns\n N_Death, Pct_Death, ct_value_Death) %>% # Death columns\n arrange(N_Known) # Arrange rows from lowest to highest (Total row at bottom)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 6 × 8\n# Groups: hospital [6]\n hospital N_Known N_Recover Pct_Recover ct_value_Recover N_Death Pct_Death\n \n1 St. Mark's M… 325 126 38.8% 22 199 61.2% \n2 Central Hosp… 358 165 46.1% 22 193 53.9% \n3 Other 685 290 42.3% 21 395 57.7% \n4 Military Hos… 708 309 43.6% 22 399 56.4% \n5 Port Hospital 1364 579 42.4% 21 785 57.6% \n6 Total 3440 1469 42.7% 22 1971 57.3% \n# ℹ 1 more variable: ct_value_Death \n```\n\n\n:::\n:::\n\n\nAnd then you can print this nicely as an image - below is the output printed with **flextable**. You can read more in depth about this example and how to achieve this \"pretty\" table in the [Tables for presentation](tables_presentation.qmd) page. \n\n\n::: {.cell}\n::: {.cell-output-display}\n\n```{=html}\n

    Hospital

    Total cases with known outcome

    Recovered

    Died

    Total

    % of cases

    Median CT values

    Total

    % of cases

    Median CT values

    St. Mark's Maternity Hospital (SMMH)

    325

    126

    38.8%

    22

    199

    61.2%

    22

    Central Hospital

    358

    165

    46.1%

    22

    193

    53.9%

    22

    Other

    685

    290

    42.3%

    21

    395

    57.7%

    22

    Military Hospital

    708

    309

    43.6%

    22

    399

    56.4%

    21

    Missing

    1,125

    514

    45.7%

    21

    611

    54.3%

    21

    Port Hospital

    1,364

    579

    42.4%

    21

    785

    57.6%

    22

    Total

    3,440

    1,469

    42.7%

    22

    1,971

    57.3%

    22

    \n```\n\n:::\n:::\n\n\n\n\n## **gtsummary** package {#tbl_gt} \n\nIf you want to print your summary statistics in a pretty, publication-ready graphic, you can use the **gtsummary** package and its function `tbl_summary()`. The code can seem complex at first, but the outputs look very nice and print to your RStudio Viewer panel as an HTML image. Read a [vignette here](http://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html). \n\nYou can also add the results of statistical tests to **gtsummary** tables. This process is described in the **gtsummary** section of the [Simple statistical tests](#stats_gt) page. \n\nTo introduce `tbl_summary()` we will show the most basic behavior first, which actually produces a large and beautiful table. Then, we will examine in detail how to make adjustments and more tailored tables. \n\n\n\n### Summary table {.unnumbered}\n\nThe default behavior of `tbl_summary()` is quite incredible - it takes the columns you provide and creates a summary table in one command. The function prints statistics appropriate to the column class: median and inter-quartile range (IQR) for numeric columns, and counts (%) for categorical columns. Missing values are converted to \"Unknown\". Footnotes are added to the bottom to explain the statistics, while the total N is shown at the top. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only the columns of interest\n tbl_summary() # default\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n
    CharacteristicN = 5,8881
    age_years13 (6, 23)
        Unknown86
    gender
        f2,807 (50%)
        m2,803 (50%)
        Unknown278
    outcome
        Death2,582 (57%)
        Recover1,983 (43%)
        Unknown1,323
    fever4,549 (81%)
        Unknown249
    temp38.80 (38.20, 39.20)
        Unknown149
    hospital
        Central Hospital454 (7.7%)
        Military Hospital896 (15%)
        Missing1,469 (25%)
        Other885 (15%)
        Port Hospital1,762 (30%)
        St. Mark's Maternity Hospital (SMMH)422 (7.2%)
    1 Median (IQR); n (%)
    \n
    \n```\n\n:::\n:::\n\n\n\n### Adjustments {.unnumbered} \n\nNow we will explain how the function works and how to make adjustments. The key arguments are detailed below: \n\n**`by = `** \nYou can stratify your table by a column (e.g. by `outcome`), creating a 2-way table. \n\n**`statistic = `** \nUse an equations to specify which statistics to show and how to display them. There are two sides to the equation, separated by a tilde `~`. On the right side, in quotes, is the statistical display desired, and on the left are the columns to which that display will apply. \n\n* The right side of the equation uses the syntax of `str_glue()` from **stringr** (see [Characters and Strings])(characters_strings.qmd), with the desired display string in quotes and the statistics themselves within curly brackets. You can include statistics like \"n\" (for counts), \"N\" (for denominator), \"mean\", \"median\", \"sd\", \"max\", \"min\", percentiles as \"p##\" like \"p25\", or percent of total as \"p\". See `?tbl_summary` for details. \n* For the left side of the equation, you can specify columns by name (e.g. `age` or `c(age, gender)`) or using helpers such as `all_continuous()`, `all_categorical()`, `contains()`, `starts_with()`, etc. \n\nA simple example of a `statistic = ` equation might look like below, to only print the mean of column `age_years`: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years) %>% # keep only columns of interest \n tbl_summary( # create summary table\n statistic = age_years ~ \"{mean}\") # print mean of age\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n
    CharacteristicN = 5,8881
    age_years16
        Unknown86
    1 Mean
    \n
    \n```\n\n:::\n:::\n\n\nA slightly more complex equation might look like `\"({min}, {max})\"`, incorporating the max and min values within parentheses and separated by a comma: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years) %>% # keep only columns of interest \n tbl_summary( # create summary table\n statistic = age_years ~ \"({min}, {max})\") # print min and max of age\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n
    CharacteristicN = 5,8881
    age_years(0, 84)
        Unknown86
    1 (Range)
    \n
    \n```\n\n:::\n:::\n\n\nYou can also differentiate syntax for separate columns or types of columns. In the more complex example below, the value provided to `statistc = ` is a **list** indicating that for all continuous columns the table should print mean with standard deviation in parentheses, while for all categorical columns it should print the n, denominator, and percent. \n\n**`digits = `** \nAdjust the digits and rounding. Optionally, this can be specified to be for continuous columns only (as below). \n\n**`label = `** \nAdjust how the column name should be displayed. Provide the column name and its desired label separated by a tilde. The default is the column name. \n\n**`missing_text = `** \nAdjust how missing values are displayed. The default is \"Unknown\". \n\n**`type = `** \nThis is used to adjust how many levels of the statistics are shown. The syntax is similar to `statistic = ` in that you provide an equation with columns on the left and a value on the right. Two common scenarios include: \n\n* `type = all_categorical() ~ \"categorical\"` Forces dichotomous columns (e.g. `fever` yes/no) to show all levels instead of only the “yes” row \n* `type = all_continuous() ~ \"continuous2\"` Allows multi-line statistics per variable, as shown in a later section \n\nIn the example below, each of these arguments is used to modify the original summary table: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years, gender, outcome, fever, temp, hospital) %>% # keep only columns of interest\n tbl_summary( \n by = outcome, # stratify entire table by outcome\n statistic = list(all_continuous() ~ \"{mean} ({sd})\", # stats and format for continuous columns\n all_categorical() ~ \"{n} / {N} ({p}%)\"), # stats and format for categorical columns\n digits = all_continuous() ~ 1, # rounding for continuous columns\n type = all_categorical() ~ \"categorical\", # force all categorical levels to display\n label = list( # display labels for column names\n outcome ~ \"Outcome\", \n age_years ~ \"Age (years)\",\n gender ~ \"Gender\",\n temp ~ \"Temperature\",\n hospital ~ \"Hospital\"),\n missing_text = \"Missing\" # how missing values should display\n )\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n1323 observations missing `outcome` have been removed. To include these observations, use `forcats::fct_na_value_to_level()` on `outcome` column before passing to `tbl_summary()`.\n```\n\n\n:::\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n\n\n \n \n \n \n \n \n \n
    CharacteristicDeath, N = 2,5821Recover, N = 1,9831
    Age (years)15.9 (12.3)16.1 (13.0)
        Missing3228
    Gender

        f1,227 / 2,455 (50%)953 / 1,903 (50%)
        m1,228 / 2,455 (50%)950 / 1,903 (50%)
        Missing12780
    fever

        no458 / 2,460 (19%)361 / 1,904 (19%)
        yes2,002 / 2,460 (81%)1,543 / 1,904 (81%)
        Missing12279
    Temperature38.6 (1.0)38.6 (1.0)
        Missing6055
    Hospital

        Central Hospital193 / 2,582 (7.5%)165 / 1,983 (8.3%)
        Military Hospital399 / 2,582 (15%)309 / 1,983 (16%)
        Missing611 / 2,582 (24%)514 / 1,983 (26%)
        Other395 / 2,582 (15%)290 / 1,983 (15%)
        Port Hospital785 / 2,582 (30%)579 / 1,983 (29%)
        St. Mark's Maternity Hospital (SMMH)199 / 2,582 (7.7%)126 / 1,983 (6.4%)
    1 Mean (SD); n / N (%)
    \n
    \n```\n\n:::\n:::\n\n\n\n\n### Multi-line stats for continuous variables {.unnumbered} \n\nIf you want to print multiple lines of statistics for continuous variables, you can indicate this by setting the `type = ` to \"continuous2\". You can combine all of the previously shown elements in one table by choosing which statistics you want to show. To do this you need to tell the function that you want to get a table back by entering the type as “continuous2”. The number of missing values is shown as \"Unknown\".\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinelist %>% \n select(age_years, temp) %>% # keep only columns of interest\n tbl_summary( # create summary table\n type = all_continuous() ~ \"continuous2\", # indicate that you want to print multiple statistics \n statistic = all_continuous() ~ c(\n \"{mean} ({sd})\", # line 1: mean and SD\n \"{median} ({p25}, {p75})\", # line 2: median and IQR\n \"{min}, {max}\") # line 3: min and max\n )\n```\n\n::: {.cell-output-display}\n\n```{=html}\n
    \n\n\n \n \n \n \n \n \n \n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n
    CharacteristicN = 5,888
    age_years
        Mean (SD)16 (13)
        Median (IQR)13 (6, 23)
        Range0, 84
        Unknown86
    temp
        Mean (SD)38.56 (0.98)
        Median (IQR)38.80 (38.20, 39.20)
        Range35.20, 40.80
        Unknown149
    \n
    \n```\n\n:::\n:::\n\nThere are many other ways to modify these tables, including adding p-values, adjusting color and headings, etc. Many of these are described in the documentation (enter `?tbl_summary` in Console), and some are given in the section on [statistical tests](https://epirhandbook.com/simple-statistical-tests.html). \n\n\n\n\n\n\n\n## **base** R \n\nYou can use the function `table()` to tabulate and cross-tabulate columns. Unlike the options above, you must specify the dataframe each time you reference a column name, as shown below. \n\n**_CAUTION:_** `NA` (missing) values will **not** be tabulated unless you include the argument `useNA = \"always\"` (which could also be set to \"no\" or \"ifany\").\n\n**_TIP:_** You can use the `%$%` from **magrittr** to remove the need for repeating data frame calls within **base** functions. For example the below could be written `linelist %$% table(outcome, useNA = \"always\")` \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntable(linelist$outcome, useNA = \"always\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n\n Death Recover \n 2582 1983 1323 \n```\n\n\n:::\n:::\n\n\nMultiple columns can be cross-tabulated by listing them one after the other, separated by commas. Optionally, you can assign each column a \"name\" like `Outcome = linelist$outcome`. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nage_by_outcome <- table(linelist$age_cat, linelist$outcome, useNA = \"always\") # save table as object\nage_by_outcome # print table\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n \n Death Recover \n 0-4 471 364 260\n 5-9 476 391 228\n 10-14 438 303 200\n 15-19 323 251 169\n 20-29 477 367 229\n 30-49 329 238 187\n 50-69 33 38 24\n 70+ 3 3 0\n 32 28 26\n```\n\n\n:::\n:::\n\n\n### Proportions {.unnumbered} \n\nTo return proportions, passing the above table to the function `prop.table()`. Use the `margins = ` argument to specify whether you want the proportions to be of rows (1), of columns (2), or of the whole table (3). For clarity, we pipe the table to the `round()` function from **base** R, specifying 2 digits. \n\n\n::: {.cell}\n\n```{.r .cell-code}\n# get proportions of table defined above, by rows, rounded\nprop.table(age_by_outcome, 1) %>% round(2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n \n Death Recover \n 0-4 0.43 0.33 0.24\n 5-9 0.43 0.36 0.21\n 10-14 0.47 0.32 0.21\n 15-19 0.43 0.34 0.23\n 20-29 0.44 0.34 0.21\n 30-49 0.44 0.32 0.25\n 50-69 0.35 0.40 0.25\n 70+ 0.50 0.50 0.00\n 0.37 0.33 0.30\n```\n\n\n:::\n:::\n\n\n### Totals {.unnumbered} \n\nTo add row and column totals, pass the table to `addmargins()`. This works for both counts and proportions. \n\n\n::: {.cell}\n\n```{.r .cell-code}\naddmargins(age_by_outcome)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n \n Death Recover Sum\n 0-4 471 364 260 1095\n 5-9 476 391 228 1095\n 10-14 438 303 200 941\n 15-19 323 251 169 743\n 20-29 477 367 229 1073\n 30-49 329 238 187 754\n 50-69 33 38 24 95\n 70+ 3 3 0 6\n 32 28 26 86\n Sum 2582 1983 1323 5888\n```\n\n\n:::\n:::\n\n\n### Convert to data frame {.unnumbered} \n\nConverting a `table()` object directly to a data frame is not straight-forward. One approach is demonstrated below: \n\n1) Create the table, *without using* `useNA = \"always\"`. Instead convert `NA` values to \"(Missing)\" with `fct_explicit_na()` from **forcats**. \n2) Add totals (optional) by piping to `addmargins()` \n3) Pipe to the **base** R function `as.data.frame.matrix()` \n4) Pipe the table to the **tibble** function `rownames_to_column()`, specifying the name for the first column \n5) Print, View, or export as desired. In this example we use `flextable()` from package **flextable** as described in the [Tables for presentation](tables_presentation.qmd) page. This will print to the RStudio viewer pane as a pretty HTML image. \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntable(fct_explicit_na(linelist$age_cat), fct_explicit_na(linelist$outcome)) %>% \n addmargins() %>% \n as.data.frame.matrix() %>% \n tibble::rownames_to_column(var = \"Age Category\") %>% \n flextable::flextable()\n```\n\n::: {.cell-output-display}\n\n```{=html}\n

    Age Category

    Death

    Recover

    (Missing)

    Sum

    0-4

    471

    364

    260

    1,095

    5-9

    476

    391

    228

    1,095

    10-14

    438

    303

    200

    941

    15-19

    323

    251

    169

    743

    20-29

    477

    367

    229

    1,073

    30-49

    329

    238

    187

    754

    50-69

    33

    38

    24

    95

    70+

    3

    3

    0

    6

    (Missing)

    32

    28

    26

    86

    Sum

    2,582

    1,983

    1,323

    5,888

    \n```\n\n:::\n:::\n\n\n\n\n\n\n\n## Resources { }\n\nMuch of the information in this page is adapted from these resources and vignettes online: \n\n[gtsummary](http://www.danieldsjoberg.com/gtsummary/articles/tbl_summary.html) \n\n[dplyr](https://dplyr.tidyverse.org/articles/grouping.html)\n\n", + "supporting": [ + "tables_descriptive_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-in-header": [ + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/new_pages/tables_descriptive_files/figure-html/unnamed-chunk-29-1.png b/new_pages/tables_descriptive_files/figure-html/unnamed-chunk-29-1.png new file mode 100644 index 00000000..91f21c6d Binary files /dev/null and b/new_pages/tables_descriptive_files/figure-html/unnamed-chunk-29-1.png differ diff --git a/new_pages/tables_presentation.qmd b/new_pages/tables_presentation.qmd index 1aeab2f7..543b6f40 100644 --- a/new_pages/tables_presentation.qmd +++ b/new_pages/tables_presentation.qmd @@ -120,16 +120,17 @@ pacman::p_load( here, # file pathways flextable, # make HTML tables officer, # helper functions for tables - tidyverse) # data management, summary, and visualization + tidyverse # data management, summary, and visualization + ) ``` ### Import data {.unnumbered} -To begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). +To begin, we import the cleaned linelist of cases from a simulated Ebola epidemic. If you want to follow along, click to download the "clean" linelist (as .rds file). Import data with the `import()` function from the **rio** package (it handles many file types like .xlsx, .csv, .rds - see the [Import and export](importing.qmd) page for details). -```{r, echo=F} +```{r, echo=F, message=F, warning=F} # import the linelist into R linelist <- rio::import(here::here("data", "case_linelists", "linelist_cleaned.rds")) ``` @@ -293,7 +294,7 @@ my_table # print ### Borders and background {.unnumbered} -You can adjust the borders, internal lines, etc. with various **flextable** functions. It is often easier to start by removing all existing borders with `border_remove()`. +You can adjust the borders, internal lines, etc., with various **flextable** functions. It is often easier to start by removing all existing borders with `border_remove()`. Then, you can apply default border themes by passing the table to `theme_box()`, `theme_booktabs()`, or `theme_alafoli()`. @@ -348,7 +349,7 @@ my_table We can ensure that the proportion columns display only one decimal place using the function `colformat_num()`. Note this could also have been done at data management stage with the `round()` function. ```{r} -my_table <- colformat_num(my_table, j = c(4,7), digits = 1) +my_table <- colformat_num(my_table, j = c(4, 7), digits = 1) my_table ``` @@ -384,7 +385,7 @@ We can highlight all values in a column that meet a certain rule, e.g. where mor ```{r} my_table %>% - bg(j = 7, i = ~ Pct_Death >= 55, part = "body", bg = "red") + bg(j = 7, i = ~ Pct_Death > 55, part = "body", bg = "red") ``` @@ -536,8 +537,10 @@ See detail in the [Reports with R Markdown](rmarkdown.qmd) page of this handbook ## Resources { } -The full **flextable** book is here: https://ardata-fr.github.io/flextable-book/ -The Github site is [here](https://davidgohel.github.io/flextable/) -A manual of all the **flextable** functions can be found [here](https://davidgohel.github.io/flextable/reference/index.html) +The full **flextable** book is [here](https://ardata-fr.github.io/flextable-book/). + +The Github site is [here](https://davidgohel.github.io/flextable/). + +A manual of all the **flextable** functions can be found [here](https://davidgohel.github.io/flextable/reference/index.html). -A gallery of beautiful example **flextable** tables with code can be accessed [here](https://ardata-fr.github.io/flextable-gallery/gallery/) +A gallery of beautiful example **flextable** tables with code can be accessed [here](https://ardata.fr/en/flextable-gallery/). diff --git a/new_pages/time_series.qmd b/new_pages/time_series.qmd index e4151a7d..e68cf6e1 100644 --- a/new_pages/time_series.qmd +++ b/new_pages/time_series.qmd @@ -17,12 +17,12 @@ with multiple countries or other strata, then there is an example code template Topics covered include: -1. Time series data -2. Descriptive analysis -3. Fitting regressions -4. Relation of two time series -5. Outbreak detection -6. Interrupted time series +1. Time series data. +2. Descriptive analysis. +3. Fitting regressions. +4. Relation of two time series. +5. Outbreak detection. +6. Interrupted time series. @@ -33,29 +33,30 @@ Topics covered include: This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. ```{r load_packages} -pacman::p_load(rio, # File import - here, # File locator - tsibble, # handle time series datasets - slider, # for calculating moving averages - imputeTS, # for filling in missing values - feasts, # for time series decomposition and autocorrelation - forecast, # fit sin and cosin terms to data (note: must load after feasts) - trending, # fit and assess models - tmaptools, # for getting geocoordinates (lon/lat) based on place names - ecmwfr, # for interacting with copernicus sateliate CDS API - stars, # for reading in .nc (climate data) files - units, # for defining units of measurement (climate data) - yardstick, # for looking at model accuracy - surveillance, # for aberration detection - tidyverse # data management + ggplot2 graphics - ) +pacman::p_load( + rio, # File import + here, # File locator + tsibble, # handle time series datasets + slider, # for calculating moving averages + imputeTS, # for filling in missing values + feasts, # for time series decomposition and autocorrelation + forecast, # fit sin and cosin terms to data (note: must load after feasts) + trending, # fit and assess models + tmaptools, # for getting geocoordinates (lon/lat) based on place names + ecmwfr, # for interacting with copernicus sateliate CDS API + stars, # for reading in .nc (climate data) files + units, # for defining units of measurement (climate data) + yardstick, # for looking at model accuracy + surveillance, # for aberration detection + tidyverse # data management + ggplot2 graphics + ) ``` ### Load data {.unnumbered} You can download all the data used in this handbook via the instructions in the [Download handbook and data](data_used.qmd) page. -The example dataset used in this section is weekly counts of campylobacter cases reported in Germany between 2001 and 2011. +The example dataset used in this section is weekly counts of campylobacter cases reported in Germany between 2001 and 2011. You can click here to download this data file (.xlsx). This dataset is a reduced version of the dataset available in the [**surveillance**](https://cran.r-project.org/web/packages/surveillance/) package. @@ -136,13 +137,11 @@ request_coords <- str_glue_data(coords$coords, "{y}/{x}/{y}/{x}") ## Pulling data modelled from copernicus satellite (ERA-5 reanalysis) -## https://cds.climate.copernicus.eu/cdsapp#!/software/app-era5-explorer?tab=app +## https://cds-beta.climate.copernicus.eu/datasets/reanalysis-era5-land?tab=overview ## https://github.com/bluegreen-labs/ecmwfr ## set up key for weather data -wf_set_key(user = "XXXXX", - key = "XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX", - service = "cds") +wf_set_key(key = "XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX") ## run for each year of interest (otherwise server times out) for (i in 2002:2011) { @@ -194,12 +193,12 @@ file_paths <- list.files( file_paths <- file_paths[str_detect(file_paths, "germany")] ## read in all the files as a stars object -data <- stars::read_stars(file_paths) +data <- stars::read_stars(file_paths, quiet = TRUE) ``` Once these files have been imported as the object `data`, we will convert them to a data frame. -```{r} +```{r, message=F} ## change to a data frame temp_data <- as_tibble(data) %>% ## add in variables and correct units @@ -440,9 +439,9 @@ Classical decomposition is used to break a time series down several parts, which when taken together make up for the pattern you see. These different parts are: -* The trend-cycle (the long-term direction of the data) -* The seasonality (repeating patterns) -* The random (what is left after removing trend and season) +* The trend-cycle (the long-term direction of the data). +* The seasonality (repeating patterns). +* The random (what is left after removing trend and season). ```{r decomposition, warning=F, message=F} @@ -507,12 +506,12 @@ this is often the most appropriate for counts data in infectious diseases. ### Fourier terms {.unnumbered} -Fourier terms are the equivalent of sin and cosin curves. The difference is that +Fourier terms are the equivalent of sine and cosine curves. The difference is that these are fit based on finding the most appropriate combination of curves to explain your data. -If only fitting one fourier term, this would be the equivalent of fitting a sin -and a cosin for your most frequently occurring lag seen in your periodogram (in our +If only fitting one fourier term, this would be the equivalent of fitting a sine +and a cosine for your most frequently occurring lag seen in your periodogram (in our case 52 weeks). We use the `fourier()` function from the **forecast** package. In the below code we assign using the `$`, as `fourier()` returns two columns (one @@ -651,7 +650,7 @@ campylobacter case counts. ### Merging datasets {.unnumbered} We can join our datasets using the week variable. For more on merging see the -handbook section on [joining](https://epirhandbook.com/joining-data.html). +handbook section on [joining](joining_matching.qmd). ```{r join} @@ -1118,7 +1117,8 @@ it is not the final model that you are using for prediction. An alternative is to use a method called cross-validation. In this scenario you roll over all of the data available to fit multiple models to predict one year ahead. You use more and more data in each model, as seen in the figure below from the -same [*Hyndman et al* text]((https://otexts.com/fpp3/). +same [Hyndman et al.,](https://otexts.com/fpp3/). + For example, the first model uses 2002 to predict 2003, the second uses 2002 and 2003 to predict 2004, and so on. ![](`r "https://otexts.com/fpp2/fpp_files/figure-html/cv1-1.png"`) @@ -1312,7 +1312,7 @@ to calculate the "control mean" (~fitted values) - it then uses a computed generalized likelihood ratio statistic to assess if there is shift in the mean for each week. Note that the threshold for each week takes in to account previous weeks so if there is a sustained shift an alarm will be triggered. -(Also note that after each alarm the algorithm is reset) +(Also note that after each alarm the algorithm is reset). In order to work with the **surveillance** package, we first need to define a "surveillance time series" object (using the `sts()` function) to fit within the @@ -1556,10 +1556,8 @@ observed <- predict(fitted_model, simulate_pi = FALSE) ```{r table_regression, eval = FALSE, echo = TRUE} -## show estimates and percentage change in a table -fitted_model %>% - ## extract original negative binomial regression - get_model() %>% +## extract original negative binomial regression +fitted_model$result[[1]] %>% ## get a tidy dataframe of results tidy(exponentiate = TRUE, conf.int = TRUE) %>% @@ -1623,9 +1621,12 @@ ggplot(estimate_res, aes(x = epiweek)) + ## Resources { } -[forecasting: principles and practice textbook](https://otexts.com/fpp3/) -[EPIET timeseries analysis case studies](https://github.com/EPIET/TimeSeriesAnalysis) -[Penn State course](https://online.stat.psu.edu/stat510/lesson/1) +[forecasting: principles and practice textbook](https://otexts.com/fpp3/) + +[EPIET timeseries analysis case studies](https://github.com/EPIET/TimeSeriesAnalysis) + +[Penn State course](https://online.stat.psu.edu/stat510/lesson/1) + [Surveillance package manuscript](https://www.jstatsoft.org/article/view/v070i10) diff --git a/new_pages/transition_to_R.html b/new_pages/transition_to_R.html index 1750078d..bd86f292 100644 --- a/new_pages/transition_to_R.html +++ b/new_pages/transition_to_R.html @@ -2,7 +2,7 @@ - + @@ -33,6 +33,8 @@ + + @@ -75,42 +77,176 @@ } - - + + + +
    - × - Need help learning R? Enroll in Applied Epi's intro R course, try our free R tutorials, post in our Community Q&A forum, or ask about our R Help Desk service. + + +
    - - + + + + + + - + + + + + + + + + + + + @@ -118,6 +254,7 @@ +

    Tidy data

    Use machine-readable “tidy” data instead of messy “human-readable” data. These are the three main requirements for “tidy” data, as explained in this tutorial on “tidy” data in R:

      -
    • Each variable must have its own column
      +
    • Each variable must have its own column.
    • -
    • Each observation must have its own row
      +
    • Each observation must have its own row.
    • -
    • Each value must have its own cell
    • +
    • Each value must have its own cell.

    To Excel users - think of the role that Excel “tables” play in standardizing data and making the format more predictable.

    An example of “tidy” data would be the case linelist used throughout this handbook - each variable is contained within one column, each observation (one case) has it’s own row, and every value is in just one cell. Below you can view the first 50 rows of the linelist:

    +
    +
    Warning: The `trust` argument of `import()` should be explicit for serialization formats
    +as of rio 1.0.3.
    +ℹ Missing `trust` will be set to FALSE by default for RDS in 2.0.0.
    +ℹ The deprecated feature was likely used in the rio package.
    +  Please report the issue at <https://github.com/gesistsa/rio/issues>.
    +
    +
    +
    -
    - +
    +
    -

    The main reason one encounters non-tidy data is because many Excel spreadsheets are designed to prioritize easy reading by humans, not easy reading by machines/software.

    +

    The main reason you might encounter non-tidy data is because many Excel spreadsheets are designed to prioritize easy reading by humans, not easy reading by machines/software.

    To help you see the difference, below are some fictional examples of non-tidy data that prioritize human-readability over machine-readability:

    @@ -722,7 +868,7 @@

    + @@ -761,7 +907,7 @@

    +

    STATA
    @@ -784,7 +930,7 @@

    +

    STATA
    @@ -815,7 +961,7 @@

    +

    STATA
    @@ -850,7 +996,7 @@

    +

    STATA
    @@ -870,11 +1016,8 @@

    R and Stata Equivalencies +
  • R for Stata users
  • @@ -883,7 +1026,7 @@

    +

    STATA
    @@ -914,7 +1057,7 @@

    +

    SAS
    @@ -924,7 +1067,7 @@

    - + @@ -937,7 +1080,7 @@

    +

    SASWorking directories can be either absolute, or relative to a project root folder by defining the root folder using %let rootdir=/root path; %include “&rootdir/subfoldername/filename”Working directories can be either absolute, or relative to a project root folder by using the here package (see Import and export))Working directories can be either absolute, or relative to a project root folder by using the here package (see Import and export)
    See current working directory with %put %sysfunc(getoption(work));
    @@ -950,7 +1093,7 @@

    Import and export)

    - + @@ -964,7 +1107,7 @@

    +

    SAS
    Reading in csv files is done by using Proc Import datafile=”filename.csv” out=work.filename dbms=CSV; run; OR using Data Step Infile statementReading in csv files is done by using Proc Import datafile=”filename.csv” out=work.filename dbms=CSV; run; OR using Data Step Infile statement Use import("filename.csv")
    @@ -1007,7 +1150,7 @@

    +

    SAS
    @@ -1025,18 +1168,20 @@

    - +
    SASCross-tabulation of two variables in a 2x2 table is done with proc freq data=Dataset; Tables rowvar*colvar; Run;Again, you can use table(), tabyl() or other options as described in the Descriptive tables page.Again, you can use table(), tabyl() or other options as described in the Descriptive tables page.

    Some useful resources:

    +

    SAS for R Users: A Book for Data Scientists (2019)

    +

    Analyzing Health Data in R for SAS Users (2018)

    R for SAS and SPSS Users (2011)

    SAS and R, Second Edition (2014)

    4.4 Data interoperability

    -

    see the Import and export(importing.qmd) page for details on how the R package rio can import and export files such as STATA .dta files, SAS .xpt and.sas7bdat files, SPSS .por and.sav files, and many others.

    +

    see the Import and export page for details on how the R package rio can import and export files such as STATA .dta files, SAS .xpt and.sas7bdat files, SPSS .por and.sav files, and many others.

    @@ -1265,6 +1410,24 @@

    { + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool)'); + for (var i=0; i -