diff --git a/.Rbuildignore b/.Rbuildignore index 5bf89a7..ecb88ea 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -23,3 +23,5 @@ ^pkgdown$ ^cran-comments\.md$ ^CRAN-RELEASE$ +^CODE_OF_CONDUCT\.md$ +^codecov\.yml$ diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index b52c263..361d8bd 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,48 +1,126 @@ -# Contributor Code of Conduct +# Contributor Covenant Code of Conduct -As contributors and maintainers of this project, and in the interest of -fostering an open and welcoming community, we pledge to respect all people who -contribute through reporting issues, posting feature requests, updating -documentation, submitting pull requests or patches, and other activities. +## Our Pledge -We are committed to making participation in this project a harassment-free -experience for everyone, regardless of level of experience, gender, gender -identity and expression, sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. -Examples of unacceptable behavior by participants include: +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing other's private information, such as physical or electronic - addresses, without explicit permission -* Other unethical or unprofessional conduct +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +## Scope -By adopting this Code of Conduct, project maintainers commit themselves to -fairly and consistently applying these principles to every aspect of managing -this project. Project maintainers who do not follow or enforce the Code of -Conduct may be permanently removed from the project team. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. +## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting a project maintainer at contact@guevarra.io. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. Maintainers are -obligated to maintain confidentiality with regard to the reporter of an incident. +reported to the community leaders responsible for enforcement at ernest@guevarra.io. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.3.0, available at -[http://contributor-covenant.org/version/1/3/0/][version] +version 2.1, available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at . -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/3/0/ +[homepage]: https://www.contributor-covenant.org diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a60010e..c524b87 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Bugs -* Submit an issue on the [issues page](https://github.com/como-ph/oxcgrt/issues) +* Submit an issue on the [issues page](https://github.com/nutriverse/nipnTK/issues) ## Code contributions @@ -11,14 +11,14 @@ * Clone your version on your account down to your machine from your account ``` -git clone https://github.com//oxcgrt.git +git clone https://github.com//nipnTK.git ``` -* Make sure to track progress upstream i.e., on our version of `oxcgrt` -at `oxcgrt/covidphdata`, by doing +* Make sure to track progress upstream i.e., on our version of `nipnTK` +at `nutriverse/nipnTK`, by doing ``` -git remote add upstream https://github.com/como-ph/oxcgrt.git +git remote add upstream https://github.com/nutriverse/nipnTK.git ``` * Before making changes make sure to pull changes in from `upstream` by doing @@ -32,4 +32,4 @@ documentation * Push up changes to your account -* Submit a pull request at `como-ph/oxcgrt` +* Submit a pull request at `nutriverse/nipnTK` diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 52c2b27..a3ac618 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: config: - - {os: macOS-latest, r: 'release'} + - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} @@ -29,7 +29,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: r-lib/actions/setup-pandoc@v2 diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 5910c1a..2c5bb50 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,48 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: - - main - - master + branches: [main, master] pull_request: - branches: - - main - - master + branches: [main, master] name: test-coverage jobs: test-coverage: - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v1 + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage - - name: Query dependencies + - name: Test coverage run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) shell: Rscript {0} - - name: Cache R packages - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies + - name: Show testthat output + if: always() run: | - install.packages(c("remotes")) - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash - - name: Test coverage - run: covr::codecov() - shell: Rscript {0} + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/DESCRIPTION b/DESCRIPTION index 430d1b9..348e909 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,11 +30,12 @@ Suggests: knitr, rmarkdown, tufte, - spelling + spelling, + covr Encoding: UTF-8 Language: en-GB LazyData: true -RoxygenNote: 7.2.1 +RoxygenNote: 7.2.3 Roxygen: list(markdown = TRUE) URL: https://nutriverse.io/nipnTK/, https://github.com/nutriverse/nipnTK BugReports: https://github.com/nutriverse/nipnTK/issues diff --git a/NEWS.md b/NEWS.md index 6005d31..b8b2cc2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,18 +1,26 @@ -## nipnTK 0.1.1.9000 +# nipnTK 0.1.1.9000 Second release of `nipnTK`. This is a GitHub-only development release. In this release: -### General updates +## General updates * remove `appveyor.yml` and the Appveyor ci/cd workflow * update GitHub Actions workflow to latest 5 system standard check +* update GitHub Actions workflow for coverage testing -## nipnTK 0.1.0 +* change default git branch name from master to main -This is the first [CRAN](https://cran.r-project.org) release of `nipnTK`. +* add CITATION + +* update CONTRIBUTOR guidelines + +* upgrade website to bootstrap 5 +# nipnTK 0.1.0 + +This is the first [CRAN](https://cran.r-project.org) release of `nipnTK`. diff --git a/README.Rmd b/README.Rmd index 21afc98..e9c1cd0 100644 --- a/README.Rmd +++ b/README.Rmd @@ -21,16 +21,15 @@ library(nipnTK) [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) -[![lifecycle](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) +[![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![CRAN](https://img.shields.io/cran/v/nipnTK.svg)](https://cran.r-project.org/package=nipnTK) [![cran checks](https://cranchecks.info/badges/summary/nipnTK)](https://cran.r-project.org/web/checks/check_results_nipnTK.html) [![CRAN](https://img.shields.io/cran/l/nipnTK.svg)](https://CRAN.R-project.org/package=nipnTK) [![CRAN](http://cranlogs.r-pkg.org/badges/nipnTK)](https://cran.r-project.org/package=nipnTK) [![CRAN](http://cranlogs.r-pkg.org/badges/grand-total/nipnTK)](https://cran.r-project.org/package=nipnTK) -[![R build status](https://github.com/nutriverse/nipnTK/workflows/R-CMD-check/badge.svg)](https://github.com/nutriverse/nipnTK/actions) -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/nutriverse/nipnTK?branch=master&svg=true)](https://ci.appveyor.com/project/nutriverse/nipnTK) -[![R build status](https://github.com/nutriverse/nipnTK/workflows/test-coverage/badge.svg)](https://github.com/nutriverse/nipnTK/actions) -[![codecov](https://codecov.io/gh/nutriverse/nipnTK/branch/master/graph/badge.svg)](https://codecov.io/gh/nutriverse/nipnTK) +[![R-CMD-check](https://github.com/nutriverse/nipnTK/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/nutriverse/nipnTK/actions/workflows/R-CMD-check.yaml) +[![test-coverage](https://github.com/nutriverse/nipnTK/actions/workflows/test-coverage.yaml/badge.svg)](https://github.com/nutriverse/nipnTK/actions/workflows/test-coverage.yaml) +[![Codecov test coverage](https://codecov.io/gh/nutriverse/nipnTK/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nutriverse/nipnTK?branch=main) [![CodeFactor](https://www.codefactor.io/repository/github/nutriverse/nipntk/badge)](https://www.codefactor.io/repository/github/nutriverse/nipntk) [![DOI](https://zenodo.org/badge/118171028.svg)](https://zenodo.org/badge/latestdoi/118171028) @@ -88,6 +87,14 @@ These activities and a proposed order in which they should be performed are show knitr::include_graphics("man/figures/nipnWorkflow.png") ``` +## Citation + +If you find the `nipnTK` package useful, please cite using the suggested citation provided by a call to the `citation` function as follows: + +```{r cite} +citation("nipnTK") +``` + ## Community guidelines Feedback, bug reports and feature requests are welcome; file issues or seek support [here](https://github.com/nutriverse/nipnTK/issues). If you would like to contribute to the package, please see our [contributing guidelines](https://nutriverse.io/nipnTK/CONTRIBUTING.html). diff --git a/README.md b/README.md index d9ef620..06000d4 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,18 @@ [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) -[![lifecycle](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) +[![Lifecycle: +stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![CRAN](https://img.shields.io/cran/v/nipnTK.svg)](https://cran.r-project.org/package=nipnTK) [![cran checks](https://cranchecks.info/badges/summary/nipnTK)](https://cran.r-project.org/web/checks/check_results_nipnTK.html) [![CRAN](https://img.shields.io/cran/l/nipnTK.svg)](https://CRAN.R-project.org/package=nipnTK) [![CRAN](http://cranlogs.r-pkg.org/badges/nipnTK)](https://cran.r-project.org/package=nipnTK) [![CRAN](http://cranlogs.r-pkg.org/badges/grand-total/nipnTK)](https://cran.r-project.org/package=nipnTK) -[![R build -status](https://github.com/nutriverse/nipnTK/workflows/R-CMD-check/badge.svg)](https://github.com/nutriverse/nipnTK/actions) -[![AppVeyor Build -Status](https://ci.appveyor.com/api/projects/status/github/nutriverse/nipnTK?branch=master&svg=true)](https://ci.appveyor.com/project/nutriverse/nipnTK) -[![R build -status](https://github.com/nutriverse/nipnTK/workflows/test-coverage/badge.svg)](https://github.com/nutriverse/nipnTK/actions) -[![codecov](https://codecov.io/gh/nutriverse/nipnTK/branch/master/graph/badge.svg)](https://codecov.io/gh/nutriverse/nipnTK) +[![R-CMD-check](https://github.com/nutriverse/nipnTK/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/nutriverse/nipnTK/actions/workflows/R-CMD-check.yaml) +[![test-coverage](https://github.com/nutriverse/nipnTK/actions/workflows/test-coverage.yaml/badge.svg)](https://github.com/nutriverse/nipnTK/actions/workflows/test-coverage.yaml) +[![Codecov test +coverage](https://codecov.io/gh/nutriverse/nipnTK/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nutriverse/nipnTK?branch=main) [![CodeFactor](https://www.codefactor.io/repository/github/nutriverse/nipntk/badge)](https://www.codefactor.io/repository/github/nutriverse/nipntk) [![DOI](https://zenodo.org/badge/118171028.svg)](https://zenodo.org/badge/latestdoi/118171028) @@ -49,7 +47,7 @@ variables. ## Requirements - - [R](https://cran.r-project.org) version 3.4 or higher +- [R](https://cran.r-project.org) version 3.4 or higher Extensive use is made of the [R](https://cran.r-project.org) language and environment for statistical computing. This is a free and powerful @@ -113,6 +111,33 @@ are shown below: +## Citation + +If you find the `nipnTK` package useful, please cite using the suggested +citation provided by a call to the `citation` function as follows: + +``` r +citation("nipnTK") +#> +#> To cite nipnTK in publications use: +#> +#> Mark Myatt and Ernest Guevarra (2023). nipnTK: National Information +#> Platforms for Nutrition (NiPN) Data Quality Toolkit R package version +#> 0.1.1.9000 URL https://nutriverse.io/nipnTK/ DOI +#> 10.5281/zenodo.4297897 +#> +#> A BibTeX entry for LaTeX users is +#> +#> @Manual{, +#> title = {nipnTK: National Information Platforms for Nutrition (NiPN) Data Quality Toolkit}, +#> author = {{Mark Myatt} and {Ernest Guevarra}}, +#> year = {2023}, +#> note = {R package version 0.1.1.9000}, +#> url = {https://nutriverse.io/nipnTK/}, +#> doi = {10.5281/zenodo.4297897}, +#> } +``` + ## Community guidelines Feedback, bug reports and feature requests are welcome; file issues or diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..04c5585 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +comment: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true diff --git a/docs/404.html b/docs/404.html index 1435e81..b1aa8e0 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1,214 +1,125 @@ - - - - + + + + - - + Page not found (404) • nipnTK - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - + + +
-
- - - - +
- -
- +
+
+ - - diff --git a/docs/CODE_OF_CONDUCT.html b/docs/CODE_OF_CONDUCT.html index 77cb722..21eadbc 100644 --- a/docs/CODE_OF_CONDUCT.html +++ b/docs/CODE_OF_CONDUCT.html @@ -1,232 +1,162 @@ - - - - - - - -Contributor Code of Conduct • nipnTK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Contributor Covenant Code of Conduct • nipnTK + Skip to contents + +
-
- +
-
- +
+ - - - + diff --git a/docs/CONTRIBUTING.html b/docs/CONTRIBUTING.html index 3c37636..3206e5b 100644 --- a/docs/CONTRIBUTING.html +++ b/docs/CONTRIBUTING.html @@ -1,244 +1,116 @@ - - - - - - - -Contributing • nipnTK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Contributing • nipnTK + Skip to contents + +
-
-
+
-
- +
+ - - - + diff --git a/docs/LICENSE.html b/docs/LICENSE.html index 724ed5b..cc2db45 100644 --- a/docs/LICENSE.html +++ b/docs/LICENSE.html @@ -1,191 +1,83 @@ - - - - - - - -GNU General Public License • nipnTK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -GNU General Public License • nipnTK + + Skip to contents + +
-
- -
- +
+ - - - + diff --git a/docs/articles/ad.html b/docs/articles/ad.html index 30fc455..a2acae0 100644 --- a/docs/articles/ad.html +++ b/docs/articles/ad.html @@ -4,7 +4,8 @@ - + + Distributions of variables and indices • nipnTK @@ -12,170 +13,149 @@ - - - + + + - - + + - -
-
- +
-
- -
-

-Graphical and numerical summaries

-

Numerical summaries are useful for checking that data are within an expected range.

-

Graphical methods are often more informative than numerical summaries.

-

A key graphical method for examining the distribution of a variable is the histogram. For example:

+
+

Graphical and numerical summaries +

+

Numerical summaries are useful for checking that data are within an +expected range.

+

Graphical methods are often more informative than numerical +summaries.

+

A key graphical method for examining the distribution of a variable +is the histogram. For example:

-hist(svy$weight)
-

displays a histogram of the weight variable in the example dataset (see figure below).

+hist(svy$weight)
+

displays a histogram of the weight variable in the example dataset +(see figure below).

-

We need to be careful when examining the distribution of measurements, as they may vary by sex. For example:

+

We need to be careful when examining the distribution of +measurements, as they may vary by sex. For example:

-hist(svy$height)
-

will display the heights of both males and females. That is, it will display two separate distributions as if they were a single distribution.

+hist(svy$height)
+

will display the heights of both males and females. That is, it will +display two separate distributions as if they were a single +distribution.

-

In this case it is sensible to look at the data for males and the data for females using separate histograms:

+

In this case it is sensible to look at the data for males and the +data for females using separate histograms:

-hist(svy$height[svy$sex == 1]) 
-hist(svy$height[svy$sex == 2])
+hist(svy$height[svy$sex == 1]) +hist(svy$height[svy$sex == 2])

or using a box-plot:

-boxplot(svy$height ~ svy$sex, names = c("M", "F"), 
-        xlab = "Sex", ylab = "Height (cm)", main = "Height by sex")
+boxplot(svy$height ~ svy$sex, names = c("M", "F"), + xlab = "Sex", ylab = "Height (cm)", main = "Height by sex")

Numerical summaries can also be used:

-by(svy$height, svy$sex, summary)
+by(svy$height, svy$sex, summary)

This returns:

-
#> svy$sex: 1
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>   56.20   75.00   81.95   82.49   90.00  110.50 
-#> ------------------------------------------------------------ 
-#> svy$sex: 2
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>   58.00   73.25   80.30   81.30   88.95  109.50
+
#> svy$sex: 1
+#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
+#>   56.20   75.00   81.95   82.49   90.00  110.50 
+#> ------------------------------------------------------------ 
+#> svy$sex: 2
+#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
+#>   58.00   73.25   80.30   81.30   88.95  109.50
-
-

-Normal distributions

-

With anthropometric variables and indices we usually expect a symmetrical (or nearly symmetrical) “bell-shaped” distribution. The variables and indices of interest are usually:

+
+

Normal distributions +

+

With anthropometric variables and indices we usually expect a +symmetrical (or nearly symmetrical) “bell-shaped” distribution. The +variables and indices of interest are usually:

-hist(svy$muac) 
-hist(svy$haz) 
-hist(svy$waz) 
-hist(svy$whz)
+hist(svy$muac) +hist(svy$haz) +hist(svy$waz) +hist(svy$whz)

These plots are shown below.

Histograms showing the distribution of anthropometric indices in the example datasetHistograms showing the distribution of anthropometric indices in the example dataset

-Histograms showing the distribution of anthropometric indices in the example dataset +Histograms showing the distribution of anthropometric indices in the +example dataset

-

The number and size of the “intervals” (breaks) used when plotting a histogram is calculated to produce a useful plot. The intervals used are based on the range of the data.

-

You can specify a different set of breaks for the hist() function to use. For example:

+

The number and size of the “intervals” (breaks) used +when plotting a histogram is calculated to produce a useful plot. The +intervals used are based on the range of the data.

+

You can specify a different set of breaks for the +hist() function to use. For example:

-hist(svy$haz, breaks = "scott")
+hist(svy$haz, breaks = "scott")

-

calculates intervals using the standard deviation and the sample size. This:

+

calculates intervals using the standard deviation and the sample +size. This:

-hist(svy$haz, breaks = "FD")
+hist(svy$haz, breaks = "FD")

calculates intervals using the inter-quartile range. This:

-hist(svy$haz, breaks = 40)
+hist(svy$haz, breaks = 40)

will use about 40 intervals. This:

-hist(svy$haz, 
-     breaks = seq(from = floor(min(svy$haz)), to = ceiling(max(svy$haz)), by = 0.5))
+hist(svy$haz, + breaks = seq(from = floor(min(svy$haz)), to = ceiling(max(svy$haz)), by = 0.5))

-

uses intervals that are 0.5 z-scores wide over the full range of haz.

-

All of these plots show nearly symmetrical “bell-shaped” distributions.

-

The ideal symmetrical “bell-shaped” distribution is the normal distribution.

-

There are a number of ways of assessing whether a variable is normally distributed.

-

The first way of assessing whether a variable is normally distributed is a simple “by-eye” assessment as we have already done using histograms.

-

The NiPN data quality toolkit provides an R language function called histNormal() that can help with “by-eye” assessments by superimposing a normal curve on a histogram of the variable of interest:

+

uses intervals that are 0.5 z-scores wide over the +full range of haz.

+

All of these plots show nearly symmetrical “bell-shaped” +distributions.

+

The ideal symmetrical “bell-shaped” distribution is the normal +distribution.

+

There are a number of ways of assessing whether a variable is +normally distributed.

+

The first way of assessing whether a variable is normally distributed +is a simple “by-eye” assessment as we have already done using +histograms.

+

The NiPN data quality toolkit provides an R language function called +histNormal() that can help with “by-eye” assessments by +superimposing a normal curve on a histogram of the variable of +interest:

-histNormal(svy$muac)
-histNormal(svy$haz)
-histNormal(svy$waz)
-histNormal(svy$whz)
-

These plots are shown below. All variables appear to be approximately normally distributed.

+histNormal(svy$muac) +histNormal(svy$haz) +histNormal(svy$waz) +histNormal(svy$whz) +

These plots are shown below. All variables appear to be approximately +normally distributed.

Histograms of anthropometric indices with normal curves superimposedHistograms of anthropometric indices with normal curves superimposed

Histograms of anthropometric indices with normal curves superimposed

-

Changing the breaks parameter may make a histogram easier to “read”. For example:

+

Changing the breaks parameter may make a histogram +easier to “read”. For example:

-histNormal(svy$haz, breaks = 15)
+histNormal(svy$haz, breaks = 15)

-

Another graphical method for assessing whether a variable is normally distributed is the normal quantile-quantile plot. These are easy to produce using R.

-

The NiPN data quality toolkit provided a helper function called qqNormalPlot() that produces a slightly enhanced normal quantile-quantile plot:

+

Another graphical method for assessing whether a variable is normally +distributed is the normal quantile-quantile plot. These are +easy to produce using R.

+

The NiPN data quality toolkit provided a helper function called +qqNormalPlot() that produces a slightly enhanced normal +quantile-quantile plot:

-qqNormalPlot(svy$whz)
+qqNormalPlot(svy$whz)

-

This plot is shown below (with annotations). In this example the tails of the distribution contain more cases than would be expected in a perfectly normally distributed variable.

+

This plot is shown below (with annotations). In this example the +tails of the distribution contain more cases than would be expected in a +perfectly normally distributed variable.

-Annotated normal quantile-quantile plot of the whz variable in the example dataset

-Annotated normal quantile-quantile plot of the whz variable in the example dataset +Annotated normal quantile-quantile plot of the whz variable in the example dataset

+Annotated normal quantile-quantile plot of the whz variable in the +example dataset

We should examine all of the relevant variables:

-qqNormalPlot(svy$muac) 
-qqNormalPlot(svy$haz) 
-qqNormalPlot(svy$waz) 
-qqNormalPlot(svy$whz)
-

These plots are shown below. There is evidence of small deviations from normality in muac, haz, and whz.

+qqNormalPlot(svy$muac) +qqNormalPlot(svy$haz) +qqNormalPlot(svy$waz) +qqNormalPlot(svy$whz) +

These plots are shown below. There is evidence of small deviations +from normality in muac, haz, and +whz.

Normal quantile-quantile plots of anthropometric indices in the example datasetNormal quantile-quantile plots of anthropometric indices in the example dataset

-Normal quantile-quantile plots of anthropometric indices in the example dataset +Normal quantile-quantile plots of anthropometric indices in the example +dataset

-

A final way of assessing normality is to use a formal statistical significance test. The preferred test is the Shapiro-Wilk test of normality:

+

A final way of assessing normality is to use a formal statistical +significance test. The preferred test is the Shapiro-Wilk test of +normality:

-shapiro.test(svy$muac) 
-#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  svy$muac
-#> W = 0.99496, p-value = 0.005495
-shapiro.test(svy$haz) 
-#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  svy$haz
-#> W = 0.99348, p-value = 0.0007455
-shapiro.test(svy$waz) 
-#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  svy$waz
-#> W = 0.99827, p-value = 0.5358
-shapiro.test(svy$whz)
-#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  svy$whz
-#> W = 0.99078, p-value = 2.777e-05
-

These tests indicate that muac, haz, and whz are significantly non-normal. Examination of the histograms and the normal quantile-quantile plots show that the deviation from normality in these indices are not particular marked. All indices have symmetrical, or nearly symmetrical, “bell-shaped” distributions.

-

We need to be careful when using significance tests such as the Shapiro-Wilk test of normality because the results can be strongly influenced by the sample size.

-

Small sample sizes can lead to tests missing large effects and large sample sizes can lead to tests identifying small effects as highly significant.

-

The analysis above found some highly significant but small deviations from normality that would probably not have been detected by a significance test if a smaller sample size had been used.

-

We can simulate a considerably smaller sample size by taking, for example, every fourth muac value:

+shapiro.test(svy$muac) +#> +#> Shapiro-Wilk normality test +#> +#> data: svy$muac +#> W = 0.99496, p-value = 0.005495 +shapiro.test(svy$haz) +#> +#> Shapiro-Wilk normality test +#> +#> data: svy$haz +#> W = 0.99348, p-value = 0.0007455 +shapiro.test(svy$waz) +#> +#> Shapiro-Wilk normality test +#> +#> data: svy$waz +#> W = 0.99827, p-value = 0.5358 +shapiro.test(svy$whz) +#> +#> Shapiro-Wilk normality test +#> +#> data: svy$whz +#> W = 0.99078, p-value = 2.777e-05 +

These tests indicate that muac, +haz, and whz are significantly +non-normal. Examination of the histograms and the normal +quantile-quantile plots show that the deviation from normality in these +indices are not particular marked. All indices have symmetrical, or +nearly symmetrical, “bell-shaped” distributions.

+

We need to be careful when using significance tests such as the +Shapiro-Wilk test of normality because the results can be strongly +influenced by the sample size.

+

Small sample sizes can lead to tests missing large effects and large +sample sizes can lead to tests identifying small effects as highly +significant.

+

The analysis above found some highly significant but small deviations +from normality that would probably not have been detected by a +significance test if a smaller sample size had been used.

+

We can simulate a considerably smaller sample size by taking, for +example, every fourth muac value:

-length(svy$muac)
-#> [1] 873
-oneQuarter <- svy$muac[seq(from = 1, to = length(svy$muac), by = 4)] 
-length(oneQuarter)
-#> [1] 219
+length(svy$muac) +#> [1] 873 +oneQuarter <- svy$muac[seq(from = 1, to = length(svy$muac), by = 4)] +length(oneQuarter) +#> [1] 219

Inspecting this smaller sample graphically:

-histNormal(oneQuarter) 
-qqNormalPlot(oneQuarter)
+histNormal(oneQuarter) +qqNormalPlot(oneQuarter)

-

yields results similar to those found when the complete sample was used, but the formal test:

+

yields results similar to those found when the complete sample was +used, but the formal test:

-shapiro.test(oneQuarter)
-#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  oneQuarter
-#> W = 0.98836, p-value = 0.0724
+shapiro.test(oneQuarter) +#> +#> Shapiro-Wilk normality test +#> +#> data: oneQuarter +#> W = 0.98836, p-value = 0.0724

is no longer significant at p < 0.05.

-

If a distribution appears to be normal (i.e. has a symmetrical, or nearly symmetrical, “bell-shaped” distribution) then it is usually safe to assume normality and to use statistical procedures that assume normality. Formal tests for normality can be misleading when sample sizes of more than a few hundred cases are used. Graphical methods are not very useful when sample sizes are small. Formal test are not very useful when sample sizes are large. The sample sizes of most anthropometry surveys will be large enough to cause formal tests for normality to identify small deviations from normality as highly significant.

+

If a distribution appears to be normal (i.e. has a symmetrical, or +nearly symmetrical, “bell-shaped” distribution) then it is usually safe +to assume normality and to use statistical procedures that assume +normality. Formal tests for normality can be misleading when sample +sizes of more than a few hundred cases are used. Graphical methods are +not very useful when sample sizes are small. Formal test are not very +useful when sample sizes are large. The sample sizes of most +anthropometry surveys will be large enough to cause formal tests for +normality to identify small deviations from normality as highly +significant.

-
-

-Skew and kurtosis

-

Skew is a measure of the asymmetry of a distribution about its mean. Skew can be zero, positive, or negative. Zero skew is found when the distribution is perfectly symmetrical. Positive skew is found when there is a long right tail to the distribution and the mass of the distribution is concentrated to the left. Negative skew is found when there is a long left tail to the distribution and the mass of the distribution is concentrated to the right. We can usually see skew in histograms. We can also calculate a skewness statistic and test if this is significantly different from zero.

-

Kurtosis is a measure of how much a distribution is concentrated about the mean. Kurtosis can be zero, positive, or negative. Zero kurtosis is found when a variable is normally distributed. Positive kurtosis is found when the mass of the distribution is concentrated about the mean and there are very few values far from the mean. Negative kurtosis is found when the mass of the distribution is concentrated in the tails of the distribution. We can usually see kurtosis in histograms. We can also calculate a kurtosis statistic and test if this is significantly different from zero.

-

The NiPN data quality toolkit provides an R language function called skewKurt() that calculates skewness and kurtosis statistics and tests whether they differ significantly from zero. Here we apply the skewKurt() function to the muac variable in the example dataset:

+
+

Skew and kurtosis +

+

Skew is a measure of the asymmetry of a distribution about its mean. +Skew can be zero, positive, or negative. Zero skew is found when the +distribution is perfectly symmetrical. Positive skew is found when there +is a long right tail to the distribution and the mass of the +distribution is concentrated to the left. Negative skew is found when +there is a long left tail to the distribution and the mass of the +distribution is concentrated to the right. We can usually see skew in +histograms. We can also calculate a skewness statistic and test if this +is significantly different from zero.

+

Kurtosis is a measure of how much a distribution is concentrated +about the mean. Kurtosis can be zero, positive, or negative. Zero +kurtosis is found when a variable is normally distributed. Positive +kurtosis is found when the mass of the distribution is concentrated +about the mean and there are very few values far from the mean. Negative +kurtosis is found when the mass of the distribution is concentrated in +the tails of the distribution. We can usually see kurtosis in +histograms. We can also calculate a kurtosis statistic and test if this +is significantly different from zero.

+

The NiPN data quality toolkit provides an R language function called +skewKurt() that calculates skewness and kurtosis statistics +and tests whether they differ significantly from zero. Here we apply the +skewKurt() function to the muac variable in the example +dataset:

-skewKurt(svy$muac)
+skewKurt(svy$muac)

This returns:

-
#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = +0.0525   SE = 0.0828 z = 0.6348  p = 0.5256
-#> Kurtosis = -0.2412   SE = 0.1653 z = 1.4586  p = 0.1447
-

There is positive skew and negative kurtosis. Neither is significantly different from zero.

-

Applying the skewKurt() function to the haz variable in the example dataset:

+
#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = +0.0525   SE = 0.0828 z = 0.6348  p = 0.5256
+#> Kurtosis = -0.2412   SE = 0.1653 z = 1.4586  p = 0.1447
+

There is positive skew and negative kurtosis. Neither is +significantly different from zero.

+

Applying the skewKurt() function to the +haz variable in the example dataset:

-skewKurt(svy$haz)
+skewKurt(svy$haz)

returns:

-
#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = +0.3074   SE = 0.0828 z = 3.7149  p = 0.0002
-#> Kurtosis = +0.2074   SE = 0.1653 z = 1.2545  p = 0.2097
-

There is a positive skew and a positive kurtosis. The skew is significantly different from zero. The skew can be seen in the histogram:

+
#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = +0.3074   SE = 0.0828 z = 3.7149  p = 0.0002
+#> Kurtosis = +0.2074   SE = 0.1653 z = 1.2545  p = 0.2097
+

There is a positive skew and a positive kurtosis. The skew is +significantly different from zero. The skew can be seen in the +histogram:

-histNormal(svy$haz, breaks = "scott")
+histNormal(svy$haz, breaks = "scott")

-

Applying the skewKurt() function to the waz variable in the example dataset:

+

Applying the skewKurt() function to the +waz variable in the example dataset:

-skewKurt(svy$waz)
+skewKurt(svy$waz)

returns:

-
#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = -0.0128   SE = 0.0828 z = 0.1541  p = 0.8775
-#> Kurtosis = +0.1805   SE = 0.1653 z = 1.0919  p = 0.2749
-

There is negative skew and positive kurtosis. Neither is significantly different from zero.

-

Applying the skewKurt() function to the whz variable in the example dataset:

+
#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = -0.0128   SE = 0.0828 z = 0.1541  p = 0.8775
+#> Kurtosis = +0.1805   SE = 0.1653 z = 1.0919  p = 0.2749
+

There is negative skew and positive kurtosis. Neither is +significantly different from zero.

+

Applying the skewKurt() function to the +whz variable in the example dataset:

-skewKurt(svy$whz)
+skewKurt(svy$whz)

returns:

-
#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = +0.0823   SE = 0.0828 z = 0.9946  p = 0.3199
-#> Kurtosis = +0.7528   SE = 0.1653 z = 4.5530  p = 0.0000
-

There is a positive skew and a positive kurtosis. The kurtosis is significantly different from zero. The kurtosis can be seen on the histogram:

+
#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = +0.0823   SE = 0.0828 z = 0.9946  p = 0.3199
+#> Kurtosis = +0.7528   SE = 0.1653 z = 4.5530  p = 0.0000
+

There is a positive skew and a positive kurtosis. The kurtosis is +significantly different from zero. The kurtosis can be seen on the +histogram:

-histNormal(svy$whz, breaks = "scott")
+histNormal(svy$whz, breaks = "scott")

-

as the tall central columns that exceed the expected values shown by the overlaid normal distribution.

-

Skew and kurtosis are both used in SMART plausibility checks. Table below shows how skew and kurtosis statistics are applied by SMART.

- +

as the tall central columns that exceed the expected values shown by +the overlaid normal distribution.

+

Skew and kurtosis are both used in SMART plausibility checks. Table +below shows how skew and kurtosis statistics are applied by SMART.

+
-The range of absolute values of skewness and kurtosis statistics that are applied by SMART (2015) +The range of absolute values of skewness and kurtosis statistics that +are applied by SMART (2015)
@@ -571,560 +645,758 @@

-* This is the value of a number ignoring its negative or positive sign.
The absolute value of -0.2412 is 0.2412. The absolute value of +0.2412 is also 0.2412. +* This is the value of a number ignoring its negative or +positive sign.
The absolute value of -0.2412 is 0.2412. The absolute +value of +0.2412 is also 0.2412.
-

The whz variable in the example dataset is considered “problematic” according to this scheme because kurtosis is above 0.6.

-

Care should be exercised when using statistical significance tests to classify data as “problematic”. The use of thresholds and ranges for skew and kurtosis statistics is usually a better approach than relying on tests of statistical significance. Significance tests can be strongly affected by sample size. Small sample sizes can lead to tests missing large effects and large sample sizes can lead to tests identifying small effects as highly significant. If a distribution appears to be normal (i.e. has a symmetrical, or nearly symmetrical, “bell-shaped” distribution) then it is usually safe to assume normality and to use statistical procedures that assume normality.

-

It is important to remember that the normal distribution is a mathematical abstraction. There is nothing compelling the real world to conform to the normal distribution. The normal distribution has become reified:

+

The whz variable in the example dataset is +considered “problematic” according to this scheme because kurtosis is +above 0.6.

+

Care should be exercised when using statistical significance tests to +classify data as “problematic”. The use of thresholds and ranges for +skew and kurtosis statistics is usually a better approach than relying +on tests of statistical significance. Significance tests can be strongly +affected by sample size. Small sample sizes can lead to tests missing +large effects and large sample sizes can lead to tests identifying small +effects as highly significant. If a distribution appears to be normal +(i.e. has a symmetrical, or nearly symmetrical, “bell-shaped” +distribution) then it is usually safe to assume normality and to use +statistical procedures that assume normality.

+

It is important to remember that the normal distribution is a +mathematical abstraction. There is nothing compelling the real world to +conform to the normal distribution. The normal distribution has become +reified:

-Everyone is sure of this [the normal distribution] … experimentalists believe that it is a mathematical theorem, and the mathematicians that it is an experimentally determined fact. +Everyone is sure of this [the normal distribution] … experimentalists +believe that it is a mathematical theorem, and the mathematicians that +it is an experimentally determined fact.
— Henri Poincaré (1912), Calcul des Probabilités
-

The data we see may be representative of reality even when it fails tests for normality.

-

Tests for normality are useful when selecting statistical methods that rely on normality. They are less useful for determining data quality. If data follows a symmetrical, or nearly symmetrical, “bell-shaped” distribution then it will usually safe to use.

+

The data we see may be representative of reality even when it fails +tests for normality.

+

Tests for normality are useful when selecting statistical methods +that rely on normality. They are less useful for determining data +quality. If data follows a symmetrical, or nearly symmetrical, +“bell-shaped” distribution then it will usually safe to use.

-
-

-Deviation from normality

-

Some anthropometric survey methods (e.g. SMART) use deviations from perfect normality as an indicator of poor data quality. This is not a sensible approach because deviations from normality are not necessarily due to poor quality data; they can be due to sampling a mixed population. This is easy to demonstrate with some simulated data.

-

We will assume that we have a population consisting of two groups:

-

Group 1 : 75% of the population, mean = -0.48, sd = 0.87

-

Group 2 : 25% of the population, mean = -1.04, sd = 1.10

-

and that we take a sample size = 1000 from the whole population. We can simulate this:

+
+

Deviation from normality +

+

Some anthropometric survey methods (e.g. SMART) use deviations from +perfect normality as an indicator of poor data quality. This is not a +sensible approach because deviations from normality are not necessarily +due to poor quality data; they can be due to sampling a mixed +population. This is easy to demonstrate with some simulated data.

+

We will assume that we have a population consisting of two +groups:

+

Group 1 : 75% of the population, mean = -0.48, sd = +0.87

+

Group 2 : 25% of the population, mean = -1.04, sd = +1.10

+

and that we take a sample size = 1000 from the whole population. We +can simulate this:

-set.seed(0)
-g1 <- rnorm(n = 750, mean = -0.48, sd = 0.87) 
-g2 <- rnorm(n = 250, mean = -1.04, sd = 1.11) 
-g1g2 <- c(g1, g2)
-

The distributions in the two subgroups (g1 and g2) are both normally distributed:

+set.seed(0) +g1 <- rnorm(n = 750, mean = -0.48, sd = 0.87) +g2 <- rnorm(n = 250, mean = -1.04, sd = 1.11) +g1g2 <- c(g1, g2)
+

The distributions in the two subgroups (g1 and +g2) are both normally distributed:

+histNormal(g1) +qqNormalPlot(g1) +shapiro.test(g1) +skewKurt(g1)

-
#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  g1
-#> W = 0.99725, p-value = 0.2411
-#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = +0.1149   SE = 0.0893 z = 1.2867  p = 0.1982
-#> Kurtosis = -0.1869   SE = 0.1783 z = 1.0483  p = 0.2945
+
#> 
+#>  Shapiro-Wilk normality test
+#> 
+#> data:  g1
+#> W = 0.99725, p-value = 0.2411
+#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = +0.1149   SE = 0.0893 z = 1.2867  p = 0.1982
+#> Kurtosis = -0.1869   SE = 0.1783 z = 1.0483  p = 0.2945
+histNormal(g2) +qqNormalPlot(g2) +shapiro.test(g2) +skewKurt(g2)

-
#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  g2
-#> W = 0.9947, p-value = 0.5363
-#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = +0.0317   SE = 0.1540 z = 0.2058  p = 0.8369
-#> Kurtosis = -0.1282   SE = 0.3068 z = 0.4178  p = 0.6761
-

but the distribution in the entire sample (g1g2) is not normal:

+
#> 
+#>  Shapiro-Wilk normality test
+#> 
+#> data:  g2
+#> W = 0.9947, p-value = 0.5363
+#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = +0.0317   SE = 0.1540 z = 0.2058  p = 0.8369
+#> Kurtosis = -0.1282   SE = 0.3068 z = 0.4178  p = 0.6761
+

but the distribution in the entire sample (g1g2) is +not normal:

-histNormal(g1g2) 
-qqNormalPlot(g1g2) 
-shapiro.test(g1g2) 
-skewKurt(g1g2)
+histNormal(g1g2) +qqNormalPlot(g1g2) +shapiro.test(g1g2) +skewKurt(g1g2)

The Shapiro-Wilk test of normality returns:

-
#> 
-#>  Shapiro-Wilk normality test
-#> 
-#> data:  g1g2
-#> W = 0.99671, p-value = 0.03514
+
#> 
+#>  Shapiro-Wilk normality test
+#> 
+#> data:  g1g2
+#> W = 0.99671, p-value = 0.03514

There is statistically significant negative skew:

-
#> 
-#>  Skewness and kurtosis
-#> 
-#> Skewness = -0.1767   SE = 0.0773 z = 2.2851  p = 0.0223
-#> Kurtosis = +0.2894   SE = 0.1545 z = 1.8728  p = 0.0611
+
#> 
+#>  Skewness and kurtosis
+#> 
+#> Skewness = -0.1767   SE = 0.0773 z = 2.2851  p = 0.0223
+#> Kurtosis = +0.2894   SE = 0.1545 z = 1.8728  p = 0.0611

There is, however, nothing wrong with the sample or with the data

-

The distribution in the entire sample (g1g2) is called a “mixture of Gaussians” (the term “Gaussian” refers to the normal distribution in this context).

+

The distribution in the entire sample (g1g2) is +called a “mixture of Gaussians” (the term “Gaussian” refers to the +normal distribution in this context).

We can see this mixture of Gaussians with:

-hist(g1, col=rgb(0.2, 0.2, 0.2, 0.5),
-     breaks = seq(-5, 3, 0.5), xlab = "", main = "")
-hist(g2, col=rgb(0.8, 0.8, 0.8, 0.5), breaks = seq(-5, 3, 0.5), add = TRUE) 
-title(main = "Histogram of g1 and g2", xlab = "z-score")
+hist(g1, col=rgb(0.2, 0.2, 0.2, 0.5), + breaks = seq(-5, 3, 0.5), xlab = "", main = "") +hist(g2, col=rgb(0.8, 0.8, 0.8, 0.5), breaks = seq(-5, 3, 0.5), add = TRUE) +title(main = "Histogram of g1 and g2", xlab = "z-score")

-

In this case the mixture was already known. There are a number of methods for revealing the underlying mixture when the components of the mixture are unknown. These techniques are not covered in this toolkit. We will, however, continue with an example in which the components of the mixture are suspected.

-

We expect to see small deviations from normality in most survey datasets. This will often be the case when a survey samples subjects over a wide area covering, for example, several agro-ecological zones, socio-economic groups, or ethnic groups. This will almost always be the case, particularly with large surveys such as DHS, MICS, and national SMART surveys.

-

Another reason for non-normality is that one (or more) of the survey teams has a systematic bias in making a measurement. Identifying the “offending” survey team by examining and testing for normality separately in all combinations of data from \(n ~ – ~ 1\) survey teams can be attempted. If (e.g.) there were three teams then we would need to separately test data from:

+

In this case the mixture was already known. There are a number of +methods for revealing the underlying mixture when the components of the +mixture are unknown. These techniques are not covered in this toolkit. +We will, however, continue with an example in which the components of +the mixture are suspected.

+

We expect to see small deviations from normality in most survey +datasets. This will often be the case when a survey samples subjects +over a wide area covering, for example, several agro-ecological zones, +socio-economic groups, or ethnic groups. This will almost always be the +case, particularly with large surveys such as DHS, MICS, and national +SMART surveys.

+

Another reason for non-normality is that one (or more) of the survey +teams has a systematic bias in making a measurement. Identifying the +“offending” survey team by examining and testing for normality +separately in all combinations of data from \(n ~ – ~ 1\) survey teams can be attempted. +If (e.g.) there were three teams then we would need to separately test +data from:

Team 1 and Team 2 (Team 3 excluded)

Team 1 and Team 3 (Team 2 excluded)

Team 2 and Team 3 (Team 1 excluded)

-

to see if the deviation from normality disappears when a particular team’s data are excluded. There is, however, a problem with this type of analysis. In cluster-sampled surveys, teams often sample adjacent primary sampling units (clusters). When this occurs the “exclude one team” analysis cannot distinguish between differences due to spatial heterogeneity (i.e. patchiness) and differences due to a team having a systematic measurement bias.

+

to see if the deviation from normality disappears when a particular +team’s data are excluded. There is, however, a problem with this type of +analysis. In cluster-sampled surveys, teams often sample adjacent +primary sampling units (clusters). When this occurs the “exclude one +team” analysis cannot distinguish between differences due to spatial +heterogeneity (i.e. patchiness) and differences due to a team having a +systematic measurement bias.

-
-

-The standard deviation and alternatives

-

The standard deviation is sometimes considered to be useful measure of data quality when applied to z-scores.

-

We can use the sd() function to find the standard deviation. For example:

+
+

The standard deviation and alternatives +

+

The standard deviation is sometimes considered to be useful measure +of data quality when applied to z-scores.

+

We can use the sd() function to find the standard +deviation. For example:

-sd(svy$whz)
+sd(svy$whz)

returns:

-
#> [1] 1.323469
+
#> [1] 1.323469

1.323469

-

This may produce misleading values if applied to raw data. This procedure should only be applied to cleaned data from which erroneous data and flagged records have been censored.

-

SMART guidelines state that the acceptable range for the standard deviation of the weight-for-height z-scores (whz) is 0.8 to 1.2 when flagging criteria have been applied and flagged records have been censored. Standard deviations outside this range are considered to indicate poor survey quality. Note that SMART does not define threshold for anthropometric indices other than weight-for-height z-scores. It is important to note that a standard deviation above 1.2 may be due to sampling from a mixed population rather than due to poor data quality.

-

The flag column in the example dataset contains a flagging code in which the codes 2, 3, 6, or 7 indicate potential problems with weight and / or height. We should calculate the standard deviation of the whz variable using only the data in which records with these flagging codes are censored and there is no oedema recorded:

+

This may produce misleading values if applied to raw data. This +procedure should only be applied to cleaned data from which erroneous +data and flagged records have been censored.

+

SMART guidelines state that the acceptable range for the standard +deviation of the weight-for-height z-scores (whz) is +0.8 to 1.2 when flagging criteria have been applied and flagged records +have been censored. Standard deviations outside this range are +considered to indicate poor survey quality. Note that SMART does not +define threshold for anthropometric indices other than weight-for-height +z-scores. It is important to note that a standard deviation above 1.2 +may be due to sampling from a mixed population rather than due to poor +data quality.

+

The flag column in the example dataset contains a +flagging code in which the codes 2, 3, +6, or 7 indicate potential problems +with weight and / or height. We should calculate the standard deviation +of the whz variable using only the data in which +records with these flagging codes are censored and there is no oedema +recorded:

-sd(svy$whz[!(svy$flag %in% c(2, 3, 6, 7) | svy$oedema == 1)])
-

The ! character specifies a logical “not”. The standard deviation is, therefore, calculated using records in which the flag variable does not contain 2, 3, 6, or 7 or oedema is not recorded as being present.

-

The standard deviation for whz when flagged records and oedema cases are censored is:

-
#> [1] 1.141944
+sd(svy$whz[!(svy$flag %in% c(2, 3, 6, 7) | svy$oedema == 1)])
+

The ! character specifies a logical “not”. The standard +deviation is, therefore, calculated using records in which the +flag variable does not contain +2, 3, 6, or +7 or oedema is not recorded as being +present.

+

The standard deviation for whz when flagged records +and oedema cases are censored is:

+
#> [1] 1.141944

This is within the SMART acceptable range of 0.8 to 1.2.

-

The problem with using the standard deviation with raw data is that it is a non-robust statistic. This means that it can be strongly influenced by outliers. For example:

+

The problem with using the standard deviation with raw data is that +it is a non-robust statistic. This means that it can be strongly +influenced by outliers. For example:

-sd(c(4.55, 5.93, 2.68, 5.61, 3.53, 4.78, 3.60, 5.82, 4.41, 5.42))
+sd(c(4.55, 5.93, 2.68, 5.61, 3.53, 4.78, 3.60, 5.82, 4.41, 5.42))

returns:

-
#> [1] 1.097533
-

Adding a single outlier (e.g. data entered as 7.84 rather than as 4.78):

+
#> [1] 1.097533
+

Adding a single outlier (e.g. data entered as 7.84 +rather than as 4.78):

-sd(c(4.55, 5.93, 2.68, 5.61, 3.53, 7.84, 3.60, 5.82, 4.41, 5.42))
+sd(c(4.55, 5.93, 2.68, 5.61, 3.53, 7.84, 3.60, 5.82, 4.41, 5.42))

returns:

-
#> [1] 1.496963
-

In this example a single outlier has strongly influenced the standard deviation.

-

There are a number of robust estimators for the standard deviation. R provides the mad() function to calculate an adjusted median absolute deviation (MAD).

-

The median absolute deviation (MAD) is defined as the median of the absolute deviations from the median. It is the median of the absolute values of the differences between the individual data points and the median of the data:

-

\[ MAD ~ = ~ ( | x_i ~ - ~ median(x) | ) \]

-

The calculated MAD is adjusted to make it consistent with the standard deviation:

-

\[ \hat{\sigma} ~ = ~ k ~ \times ~ MAD \]

-

where k is a constant scaling factor, which depends upon the distribution. For the normal distribution:

+
#> [1] 1.496963
+

In this example a single outlier has strongly influenced the standard +deviation.

+

There are a number of robust estimators for the standard deviation. R +provides the mad() function to calculate an adjusted +median absolute deviation (MAD).

+

The median absolute deviation (MAD) is defined as the median of the +absolute deviations from the median. It is the median of the absolute +values of the differences between the individual data points and the +median of the data:

+

\[ MAD ~ = ~ ( | x_i ~ - ~ median(x) | ) +\]

+

The calculated MAD is adjusted to make it consistent with the +standard deviation:

+

\[ \hat{\sigma} ~ = ~ k ~ \times ~ +MAD \]

+

where k is a constant scaling factor, which depends upon the +distribution. For the normal distribution:

\[ k ~ = ~ 1.4826 \]

-

The mad() function in R function returns the adjusted MAD:

-

\[ \hat{\sigma} ~ = ~ 1.4826 ~ \times ~ MAD \]

+

The mad() function in R function returns the adjusted +MAD:

+

\[ \hat{\sigma} ~ = ~ 1.4826 ~ \times ~ +MAD \]

This is a robust estimate of the standard deviation.

-

This estimator is preferred when a sample is taken from a mixed population (this is almost always the case) and when the distribution has “fat” or “heavy” tails, as is the case with the whz variable in the example dataset.

-

Using the mad() function with the raw WHZ data:

+

This estimator is preferred when a sample is taken from a mixed +population (this is almost always the case) and when the distribution +has “fat” or “heavy” tails, as is the case with the whz +variable in the example dataset.

+

Using the mad() function with the raw WHZ data:

-mad(svy$whz)
+mad(svy$whz)

This returns:

-
#> [1] 1.156428
-

We would usually want to calculate the adjusted MAD of the whz variable using only the data in which records with flagging codes relevant to whz and cases of oedema are censored:

+
#> [1] 1.156428
+

We would usually want to calculate the adjusted MAD of the whz +variable using only the data in which records with flagging codes +relevant to whz and cases of oedema are censored:

-mad(svy$whz[!(svy$flag %in% c(2, 3, 6, 7) | svy$oedema == 1)])
+mad(svy$whz[!(svy$flag %in% c(2, 3, 6, 7) | svy$oedema == 1)])

This returns:

-
#> [1] 1.097124
-

The use of the standard deviation and robust equivalents such as the adjusted MAD with simple thresholds is problematic. Data that is a mixture of Gaussians distributions will tend to have large standard deviations even when there is no systematic error and nothing is wrong with the sample. Checks on the standard deviation in large surveys should, therefore, be performed on the smallest spatial strata above the PSU or cluster level. This reduces but does not eliminate the problem of sampling from mixed populations.

+
#> [1] 1.097124
+

The use of the standard deviation and robust equivalents such as the +adjusted MAD with simple thresholds is problematic. Data that is a +mixture of Gaussians distributions will tend to have large standard +deviations even when there is no systematic error and nothing is wrong +with the sample. Checks on the standard deviation in large surveys +should, therefore, be performed on the smallest spatial strata above the +PSU or cluster level. This reduces but does not eliminate the problem of +sampling from mixed populations.

We will retrieve a dataset and examine within-strata MADs:

-svy <- read.table("flag.ex03.csv", header = TRUE, sep = ",")
-head(svy)
-
#>   psu region state age sex weight height   haz   waz   whz
-#> 1   1     SE  Abia  12   2    7.4   72.1 -0.74 -1.58 -1.69
-#> 2   1     SE  Abia  33   1   13.3   94.2  0.04 -0.33 -0.52
-#> 3   1     SE  Abia  44   2   14.1   98.6 -0.41 -0.63 -0.57
-#> 4   1     SE  Abia  40   2   15.8   99.3  0.39  0.59  0.55
-#> 5   1     SE  Abia  23   2   10.1   83.9 -0.51 -0.90 -0.92
-#> 6   1     SE  Abia  24   1   13.9   88.7  0.52  1.18  1.22
-

The file flag.ex03.csv is a comma-separated-value (CSV) file containing anthropometric data from a national SMART survey in Nigeria.

-

The data stored in the file flag.ex03.csv were collected using methods similar to MICS and DHS surveys. The only difference is that the survey concentrated on anthropometric data in children aged between 6 and 59 months. In this exercise we will concentrate on WHZ.

-

Data are stratified by region and by state within region. We will create a new variable that combines region and state:

+svy <- read.table("flag.ex03.csv", header = TRUE, sep = ",") +head(svy) +
#>   psu region state age sex weight height   haz   waz   whz
+#> 1   1     SE  Abia  12   2    7.4   72.1 -0.74 -1.58 -1.69
+#> 2   1     SE  Abia  33   1   13.3   94.2  0.04 -0.33 -0.52
+#> 3   1     SE  Abia  44   2   14.1   98.6 -0.41 -0.63 -0.57
+#> 4   1     SE  Abia  40   2   15.8   99.3  0.39  0.59  0.55
+#> 5   1     SE  Abia  23   2   10.1   83.9 -0.51 -0.90 -0.92
+#> 6   1     SE  Abia  24   1   13.9   88.7  0.52  1.18  1.22
+

The file flag.ex03.csv is a comma-separated-value +(CSV) file containing anthropometric data from a national SMART survey +in Nigeria.

+

The data stored in the file flag.ex03.csv were collected using +methods similar to MICS and DHS surveys. The only difference is that the +survey concentrated on anthropometric data in children aged between 6 +and 59 months. In this exercise we will concentrate on WHZ.

+

Data are stratified by region and by +state within region. We will create a +new variable that combines region and +state:

-svy$regionState <- paste(svy$region, svy$state, sep = ":")
-head(svy)
-#>   psu region state age sex weight height   haz   waz   whz regionState
-#> 1   1     SE  Abia  12   2    7.4   72.1 -0.74 -1.58 -1.69     SE:Abia
-#> 2   1     SE  Abia  33   1   13.3   94.2  0.04 -0.33 -0.52     SE:Abia
-#> 3   1     SE  Abia  44   2   14.1   98.6 -0.41 -0.63 -0.57     SE:Abia
-#> 4   1     SE  Abia  40   2   15.8   99.3  0.39  0.59  0.55     SE:Abia
-#> 5   1     SE  Abia  23   2   10.1   83.9 -0.51 -0.90 -0.92     SE:Abia
-#> 6   1     SE  Abia  24   1   13.9   88.7  0.52  1.18  1.22     SE:Abia
-table(svy$regionState)
-#> 
-#>       NC:Benue NC:FCT (Abuja)        NC:Kogi       NC:Kwara    NC:Nasarawa 
-#>            386            363            326            392            430 
-#>       NC:Niger     NC:Plateau     NE:Adamawa      NE:Bauchi       NE:Borno 
-#>            589            503            410            804            558 
-#>       NE:Gombe      NE:Taraba        NE:Yobe      NW:Jigawa      NW:Kaduna 
-#>            643            421            689            711            536 
-#>        NW:Kano     NW:Katsina       NW:Kebbi      NW:Sokoto     NW:Zamfara 
-#>            671            657            728            646            668 
-#>        SE:Abia     SE:Anambra      SE:Ebonyi       SE:Enugu         SE:Imo 
-#>            334            390            455            418            371 
-#>   SS:Akwa-Ibom     SS:Bayelsa SS:Cross River       SS:Delta         SS:Edo 
-#>            331            330            376            346            480 
-#>      SS:Rivers       SW:Ekiti       SW:Lagos        SW:Ogun        SW:Ondo 
-#>            315            376            640            566            426 
-#>        SW:Osun         SW:Oyo 
-#>            435            610
-

We can examine the adjusted MAD for whz for each combination of region and state in the survey dataset using:

+svy$regionState <- paste(svy$region, svy$state, sep = ":") +head(svy) +#> psu region state age sex weight height haz waz whz regionState +#> 1 1 SE Abia 12 2 7.4 72.1 -0.74 -1.58 -1.69 SE:Abia +#> 2 1 SE Abia 33 1 13.3 94.2 0.04 -0.33 -0.52 SE:Abia +#> 3 1 SE Abia 44 2 14.1 98.6 -0.41 -0.63 -0.57 SE:Abia +#> 4 1 SE Abia 40 2 15.8 99.3 0.39 0.59 0.55 SE:Abia +#> 5 1 SE Abia 23 2 10.1 83.9 -0.51 -0.90 -0.92 SE:Abia +#> 6 1 SE Abia 24 1 13.9 88.7 0.52 1.18 1.22 SE:Abia +table(svy$regionState) +#> +#> NC:Benue NC:FCT (Abuja) NC:Kogi NC:Kwara NC:Nasarawa +#> 386 363 326 392 430 +#> NC:Niger NC:Plateau NE:Adamawa NE:Bauchi NE:Borno +#> 589 503 410 804 558 +#> NE:Gombe NE:Taraba NE:Yobe NW:Jigawa NW:Kaduna +#> 643 421 689 711 536 +#> NW:Kano NW:Katsina NW:Kebbi NW:Sokoto NW:Zamfara +#> 671 657 728 646 668 +#> SE:Abia SE:Anambra SE:Ebonyi SE:Enugu SE:Imo +#> 334 390 455 418 371 +#> SS:Akwa-Ibom SS:Bayelsa SS:Cross River SS:Delta SS:Edo +#> 331 330 376 346 480 +#> SS:Rivers SW:Ekiti SW:Lagos SW:Ogun SW:Ondo +#> 315 376 640 566 426 +#> SW:Osun SW:Oyo +#> 435 610 +

We can examine the adjusted MAD for whz for each +combination of region and state in the +survey dataset using:

-by(svy$whz, svy$regionState, mad, na.rm = TRUE)
-#> svy$regionState: NC:Benue
-#> [1] 0.941451
-#> ------------------------------------------------------------ 
-#> svy$regionState: NC:FCT (Abuja)
-#> [1] 0.96369
-#> ------------------------------------------------------------ 
-#> svy$regionState: NC:Kogi
-#> [1] 0.993342
-#> ------------------------------------------------------------ 
-#> svy$regionState: NC:Kwara
-#> [1] 0.993342
-#> ------------------------------------------------------------ 
-#> svy$regionState: NC:Nasarawa
-#> [1] 0.926625
-#> ------------------------------------------------------------ 
-#> svy$regionState: NC:Niger
-#> [1] 0.978516
-#> ------------------------------------------------------------ 
-#> svy$regionState: NC:Plateau
-#> [1] 1.022994
-#> ------------------------------------------------------------ 
-#> svy$regionState: NE:Adamawa
-#> [1] 1.045233
-#> ------------------------------------------------------------ 
-#> svy$regionState: NE:Bauchi
-#> [1] 1.18608
-#> ------------------------------------------------------------ 
-#> svy$regionState: NE:Borno
-#> [1] 1.030407
-#> ------------------------------------------------------------ 
-#> svy$regionState: NE:Gombe
-#> [1] 1.082298
-#> ------------------------------------------------------------ 
-#> svy$regionState: NE:Taraba
-#> [1] 1.008168
-#> ------------------------------------------------------------ 
-#> svy$regionState: NE:Yobe
-#> [1] 1.022994
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Jigawa
-#> [1] 1.200906
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Kaduna
-#> [1] 0.985929
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Kano
-#> [1] 1.156428
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Katsina
-#> [1] 1.022994
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Kebbi
-#> [1] 0.926625
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Sokoto
-#> [1] 0.926625
-#> ------------------------------------------------------------ 
-#> svy$regionState: NW:Zamfara
-#> [1] 1.052646
-#> ------------------------------------------------------------ 
-#> svy$regionState: SE:Abia
-#> [1] 0.904386
-#> ------------------------------------------------------------ 
-#> svy$regionState: SE:Anambra
-#> [1] 0.926625
-#> ------------------------------------------------------------ 
-#> svy$regionState: SE:Ebonyi
-#> [1] 0.904386
-#> ------------------------------------------------------------ 
-#> svy$regionState: SE:Enugu
-#> [1] 0.919212
-#> ------------------------------------------------------------ 
-#> svy$regionState: SE:Imo
-#> [1] 0.88956
-#> ------------------------------------------------------------ 
-#> svy$regionState: SS:Akwa-Ibom
-#> [1] 0.904386
-#> ------------------------------------------------------------ 
-#> svy$regionState: SS:Bayelsa
-#> [1] 1.11195
-#> ------------------------------------------------------------ 
-#> svy$regionState: SS:Cross River
-#> [1] 0.971103
-#> ------------------------------------------------------------ 
-#> svy$regionState: SS:Delta
-#> [1] 0.971103
-#> ------------------------------------------------------------ 
-#> svy$regionState: SS:Edo
-#> [1] 0.971103
-#> ------------------------------------------------------------ 
-#> svy$regionState: SS:Rivers
-#> [1] 1.052646
-#> ------------------------------------------------------------ 
-#> svy$regionState: SW:Ekiti
-#> [1] 1.030407
-#> ------------------------------------------------------------ 
-#> svy$regionState: SW:Lagos
-#> [1] 0.837669
-#> ------------------------------------------------------------ 
-#> svy$regionState: SW:Ogun
-#> [1] 0.911799
-#> ------------------------------------------------------------ 
-#> svy$regionState: SW:Ondo
-#> [1] 0.978516
-#> ------------------------------------------------------------ 
-#> svy$regionState: SW:Osun
-#> [1] 0.904386
-#> ------------------------------------------------------------ 
-#> svy$regionState: SW:Oyo
-#> [1] 0.956277
-

The long output can be made more compact, easier to read, and easier to work with:

+by(svy$whz, svy$regionState, mad, na.rm = TRUE) +#> svy$regionState: NC:Benue +#> [1] 0.941451 +#> ------------------------------------------------------------ +#> svy$regionState: NC:FCT (Abuja) +#> [1] 0.96369 +#> ------------------------------------------------------------ +#> svy$regionState: NC:Kogi +#> [1] 0.993342 +#> ------------------------------------------------------------ +#> svy$regionState: NC:Kwara +#> [1] 0.993342 +#> ------------------------------------------------------------ +#> svy$regionState: NC:Nasarawa +#> [1] 0.926625 +#> ------------------------------------------------------------ +#> svy$regionState: NC:Niger +#> [1] 0.978516 +#> ------------------------------------------------------------ +#> svy$regionState: NC:Plateau +#> [1] 1.022994 +#> ------------------------------------------------------------ +#> svy$regionState: NE:Adamawa +#> [1] 1.045233 +#> ------------------------------------------------------------ +#> svy$regionState: NE:Bauchi +#> [1] 1.18608 +#> ------------------------------------------------------------ +#> svy$regionState: NE:Borno +#> [1] 1.030407 +#> ------------------------------------------------------------ +#> svy$regionState: NE:Gombe +#> [1] 1.082298 +#> ------------------------------------------------------------ +#> svy$regionState: NE:Taraba +#> [1] 1.008168 +#> ------------------------------------------------------------ +#> svy$regionState: NE:Yobe +#> [1] 1.022994 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Jigawa +#> [1] 1.200906 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Kaduna +#> [1] 0.985929 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Kano +#> [1] 1.156428 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Katsina +#> [1] 1.022994 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Kebbi +#> [1] 0.926625 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Sokoto +#> [1] 0.926625 +#> ------------------------------------------------------------ +#> svy$regionState: NW:Zamfara +#> [1] 1.052646 +#> ------------------------------------------------------------ +#> svy$regionState: SE:Abia +#> [1] 0.904386 +#> ------------------------------------------------------------ +#> svy$regionState: SE:Anambra +#> [1] 0.926625 +#> ------------------------------------------------------------ +#> svy$regionState: SE:Ebonyi +#> [1] 0.904386 +#> ------------------------------------------------------------ +#> svy$regionState: SE:Enugu +#> [1] 0.919212 +#> ------------------------------------------------------------ +#> svy$regionState: SE:Imo +#> [1] 0.88956 +#> ------------------------------------------------------------ +#> svy$regionState: SS:Akwa-Ibom +#> [1] 0.904386 +#> ------------------------------------------------------------ +#> svy$regionState: SS:Bayelsa +#> [1] 1.11195 +#> ------------------------------------------------------------ +#> svy$regionState: SS:Cross River +#> [1] 0.971103 +#> ------------------------------------------------------------ +#> svy$regionState: SS:Delta +#> [1] 0.971103 +#> ------------------------------------------------------------ +#> svy$regionState: SS:Edo +#> [1] 0.971103 +#> ------------------------------------------------------------ +#> svy$regionState: SS:Rivers +#> [1] 1.052646 +#> ------------------------------------------------------------ +#> svy$regionState: SW:Ekiti +#> [1] 1.030407 +#> ------------------------------------------------------------ +#> svy$regionState: SW:Lagos +#> [1] 0.837669 +#> ------------------------------------------------------------ +#> svy$regionState: SW:Ogun +#> [1] 0.911799 +#> ------------------------------------------------------------ +#> svy$regionState: SW:Ondo +#> [1] 0.978516 +#> ------------------------------------------------------------ +#> svy$regionState: SW:Osun +#> [1] 0.904386 +#> ------------------------------------------------------------ +#> svy$regionState: SW:Oyo +#> [1] 0.956277 +

The long output can be made more compact, easier to read, and easier +to work with:

-mads <- by(svy$whz, svy$regionState, mad, na.rm = TRUE)
-mads <- round(mads[1:length(mads)], 2)
-mads
-#> svy$regionState
-#>       NC:Benue NC:FCT (Abuja)        NC:Kogi       NC:Kwara    NC:Nasarawa 
-#>           0.94           0.96           0.99           0.99           0.93 
-#>       NC:Niger     NC:Plateau     NE:Adamawa      NE:Bauchi       NE:Borno 
-#>           0.98           1.02           1.05           1.19           1.03 
-#>       NE:Gombe      NE:Taraba        NE:Yobe      NW:Jigawa      NW:Kaduna 
-#>           1.08           1.01           1.02           1.20           0.99 
-#>        NW:Kano     NW:Katsina       NW:Kebbi      NW:Sokoto     NW:Zamfara 
-#>           1.16           1.02           0.93           0.93           1.05 
-#>        SE:Abia     SE:Anambra      SE:Ebonyi       SE:Enugu         SE:Imo 
-#>           0.90           0.93           0.90           0.92           0.89 
-#>   SS:Akwa-Ibom     SS:Bayelsa SS:Cross River       SS:Delta         SS:Edo 
-#>           0.90           1.11           0.97           0.97           0.97 
-#>      SS:Rivers       SW:Ekiti       SW:Lagos        SW:Ogun        SW:Ondo 
-#>           1.05           1.03           0.84           0.91           0.98 
-#>        SW:Osun         SW:Oyo 
-#>           0.90           0.96
+mads <- by(svy$whz, svy$regionState, mad, na.rm = TRUE) +mads <- round(mads[1:length(mads)], 2) +mads +#> svy$regionState +#> NC:Benue NC:FCT (Abuja) NC:Kogi NC:Kwara NC:Nasarawa +#> 0.94 0.96 0.99 0.99 0.93 +#> NC:Niger NC:Plateau NE:Adamawa NE:Bauchi NE:Borno +#> 0.98 1.02 1.05 1.19 1.03 +#> NE:Gombe NE:Taraba NE:Yobe NW:Jigawa NW:Kaduna +#> 1.08 1.01 1.02 1.20 0.99 +#> NW:Kano NW:Katsina NW:Kebbi NW:Sokoto NW:Zamfara +#> 1.16 1.02 0.93 0.93 1.05 +#> SE:Abia SE:Anambra SE:Ebonyi SE:Enugu SE:Imo +#> 0.90 0.93 0.90 0.92 0.89 +#> SS:Akwa-Ibom SS:Bayelsa SS:Cross River SS:Delta SS:Edo +#> 0.90 1.11 0.97 0.97 0.97 +#> SS:Rivers SW:Ekiti SW:Lagos SW:Ogun SW:Ondo +#> 1.05 1.03 0.84 0.91 0.98 +#> SW:Osun SW:Oyo +#> 0.90 0.96

The saved mads object can be summarised:

-summary(mads)
+summary(mads)

This returns:

-
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>  0.8400  0.9300  0.9800  0.9892  1.0300  1.2000
+
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
+#>  0.8400  0.9300  0.9800  0.9892  1.0300  1.2000

A table can also be useful:

-table(mads)
-#> mads
-#> 0.84 0.89  0.9 0.91 0.92 0.93 0.94 0.96 0.97 0.98 0.99 1.01 1.02 1.03 1.05 1.08 
-#>    1    1    4    1    1    4    1    2    3    2    3    1    3    2    3    1 
-#> 1.11 1.16 1.19  1.2 
-#>    1    1    1    1
-

In this example the adjusted MAD of the whz variable is within the limits 0.8 to 1.2 for all combinations of region and state.

-

Note that we combined region and state. We did this to avoid potential problems with duplicate state names (i.e. the same state name used in more than one region).

-

In the previous exercise we used the raw (i.e. without flagging) data. It is better to use only the data in which records with flagging codes relevant to whz and cases of oedema are censored.

-

This is a national SMART survey so we will use SMART flagging criteria. We will use the national.SMART() function to add SMART flags to the survey dataset:

+table(mads) +#> mads +#> 0.84 0.89 0.9 0.91 0.92 0.93 0.94 0.96 0.97 0.98 0.99 1.01 1.02 1.03 1.05 1.08 +#> 1 1 4 1 1 4 1 2 3 2 3 1 3 2 3 1 +#> 1.11 1.16 1.19 1.2 +#> 1 1 1 1 +

In this example the adjusted MAD of the whz variable +is within the limits 0.8 to 1.2 for all combinations of +region and state.

+

Note that we combined region and +state. We did this to avoid potential problems with +duplicate state names (i.e. the same +state name used in more than one +region).

+

In the previous exercise we used the raw (i.e. without flagging) +data. It is better to use only the data in which records with flagging +codes relevant to whz and cases of oedema are +censored.

+

This is a national SMART survey so we will use SMART flagging +criteria. We will use the national.SMART() function to add +SMART flags to the survey dataset:

-svyFlagged <- national.SMART(x = svy, strata = "regionState")
+svyFlagged <- national.SMART(x = svy, strata = "regionState")

We need to exclude records with flagging codes relevant to whz:

-svyFlagged <- svyFlagged[!(svyFlagged$flagSMART %in% c(2, 3, 6, 7)), ]
-

Note that oedema is not recorded in the dataset so we cannot exclude oedema cases.

-

We can now calculate the MAD for whz in each stratum:

+svyFlagged <- svyFlagged[!(svyFlagged$flagSMART %in% c(2, 3, 6, 7)), ] +

Note that oedema is not recorded in the dataset so we cannot exclude +oedema cases.

+

We can now calculate the MAD for whz in each +stratum:

-mads <- by(svyFlagged$whz, svyFlagged$regionState, mad, na.rm = TRUE)
-mads <- round(mads[1:length(mads)], 2)
-mads
-#> svyFlagged$regionState
-#>       NC:Benue NC:FCT (Abuja)        NC:Kogi       NC:Kwara    NC:Nasarawa 
-#>           0.92           0.95           0.99           0.96           0.92 
-#>       NC:Niger     NC:Plateau     NE:Adamawa      NE:Bauchi       NE:Borno 
-#>           0.93           1.02           1.02           1.17           1.02 
-#>       NE:Gombe      NE:Taraba        NE:Yobe      NW:Jigawa      NW:Kaduna 
-#>           1.06           0.98           0.99           1.17           0.96 
-#>        NW:Kano     NW:Katsina       NW:Kebbi      NW:Sokoto     NW:Zamfara 
-#>           1.10           1.01           0.90           0.90           1.02 
-#>        SE:Abia     SE:Anambra      SE:Ebonyi       SE:Enugu         SE:Imo 
-#>           0.87           0.91           0.90           0.90           0.87 
-#>   SS:Akwa-Ibom     SS:Bayelsa SS:Cross River       SS:Delta         SS:Edo 
-#>           0.87           1.05           0.92           0.95           0.96 
-#>      SS:Rivers       SW:Ekiti       SW:Lagos        SW:Ogun        SW:Ondo 
-#>           1.01           1.01           0.85           0.90           0.96 
-#>        SW:Osun         SW:Oyo 
-#>           0.89           0.95
+mads <- by(svyFlagged$whz, svyFlagged$regionState, mad, na.rm = TRUE) +mads <- round(mads[1:length(mads)], 2) +mads +#> svyFlagged$regionState +#> NC:Benue NC:FCT (Abuja) NC:Kogi NC:Kwara NC:Nasarawa +#> 0.92 0.95 0.99 0.96 0.92 +#> NC:Niger NC:Plateau NE:Adamawa NE:Bauchi NE:Borno +#> 0.93 1.02 1.02 1.17 1.02 +#> NE:Gombe NE:Taraba NE:Yobe NW:Jigawa NW:Kaduna +#> 1.06 0.98 0.99 1.17 0.96 +#> NW:Kano NW:Katsina NW:Kebbi NW:Sokoto NW:Zamfara +#> 1.10 1.01 0.90 0.90 1.02 +#> SE:Abia SE:Anambra SE:Ebonyi SE:Enugu SE:Imo +#> 0.87 0.91 0.90 0.90 0.87 +#> SS:Akwa-Ibom SS:Bayelsa SS:Cross River SS:Delta SS:Edo +#> 0.87 1.05 0.92 0.95 0.96 +#> SS:Rivers SW:Ekiti SW:Lagos SW:Ogun SW:Ondo +#> 1.01 1.01 0.85 0.90 0.96 +#> SW:Osun SW:Oyo +#> 0.89 0.95

The saved mads object can be summarised:

-summary(mads)
+summary(mads)

This returns:

-
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>  0.8500  0.9000  0.9600  0.9665  1.0100  1.1700
-

In this analysis the adjusted MAD of the whz variable is within the limits 0.8 to 1.2 for all combinations of region and state.

+
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
+#>  0.8500  0.9000  0.9600  0.9665  1.0100  1.1700
+

In this analysis the adjusted MAD of the whz +variable is within the limits 0.8 to 1.2 for all combinations of +region and state.

-
-

-Measures of dispersion

-

Measures of dispersion summarise how cases (e.g. children classified as wasted, stunted, or underweight) are distributed across a survey’s primary sampling units (e.g. clusters).

+
+

Measures of dispersion +

+

Measures of dispersion summarise how cases (e.g. children classified +as wasted, stunted, or underweight) are distributed across a survey’s +primary sampling units (e.g. clusters).

We will retrieve a survey dataset:

-svy <- read.table("flag.ex01.csv", header = TRUE, sep = ",") 
-head(svy)
-
#>   psu child age sex weight height muac oedema   haz   waz   whz
-#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03
-#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93
-#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25
-#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57
-#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61
-#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82
-

The file flag.ex01.csv is a comma-separated-value (CSV) file containing anthropometric data from a recent SMART survey in Sudan.

+svy <- read.table("flag.ex01.csv", header = TRUE, sep = ",") +head(svy)
+
#>   psu child age sex weight height muac oedema   haz   waz   whz
+#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03
+#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93
+#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25
+#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57
+#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61
+#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82
+

The file flag.ex01.csv is a comma-separated-value +(CSV) file containing anthropometric data from a recent SMART survey in +Sudan.

We will apply WHO flagging criteria to the data:

-svy$flag <- 0
-svy$flag <- ifelse(!is.na(svy$haz) & (svy$haz < -6 | svy$haz > 6), svy$flag + 1, svy$flag)
-svy$flag <- ifelse(!is.na(svy$whz) & (svy$whz < -5 | svy$whz > 5), svy$flag + 2, svy$flag)
-svy$flag <- ifelse(!is.na(svy$waz) & (svy$waz < -6 | svy$waz > 5), svy$flag + 4, svy$flag)
+svy$flag <- 0 +svy$flag <- ifelse(!is.na(svy$haz) & (svy$haz < -6 | svy$haz > 6), svy$flag + 1, svy$flag) +svy$flag <- ifelse(!is.na(svy$whz) & (svy$whz < -5 | svy$whz > 5), svy$flag + 2, svy$flag) +svy$flag <- ifelse(!is.na(svy$waz) & (svy$waz < -6 | svy$waz > 5), svy$flag + 4, svy$flag)

We should exclude flagged records:

-svy <- svy[svy$flag == 0, ]
+svy <- svy[svy$flag == 0, ]

We will apply a case-definition for being stunted:

-svy$stunted <- ifelse(svy$haz < -2, 1, 2)
-

We can examine the distribution of stunted cases across the primary sampling units in this survey:

+svy$stunted <- ifelse(svy$haz < -2, 1, 2) +

We can examine the distribution of stunted cases across the primary +sampling units in this survey:

-table(svy$psu, svy$stunted)
-#>     
-#>       1  2
-#>   1   8 20
-#>   2  11 14
-#>   3   7 22
-#>   4   6 23
-#>   5   7 15
-#>   6  11 20
-#>   7  11 14
-#>   8  14 12
-#>   9  12 18
-#>   10 10  9
-#>   11 12 16
-#>   12  9 13
-#>   13  9 13
-#>   14  5 21
-#>   15 12  9
-#>   16  8 17
-#>   17  6 23
-#>   18  8 21
-#>   19 10 12
-#>   20  6 20
-#>   21 11 18
-#>   22 11 14
-#>   23 12  6
-#>   24  8 15
-#>   25 10 19
-#>   26 10  8
-#>   27 12  9
-#>   28  6 14
-#>   29 14 10
-#>   30 11 18
+table(svy$psu, svy$stunted) +#> +#> 1 2 +#> 1 8 20 +#> 2 11 14 +#> 3 7 22 +#> 4 6 23 +#> 5 7 15 +#> 6 11 20 +#> 7 11 14 +#> 8 14 12 +#> 9 12 18 +#> 10 10 9 +#> 11 12 16 +#> 12 9 13 +#> 13 9 13 +#> 14 5 21 +#> 15 12 9 +#> 16 8 17 +#> 17 6 23 +#> 18 8 21 +#> 19 10 12 +#> 20 6 20 +#> 21 11 18 +#> 22 11 14 +#> 23 12 6 +#> 24 8 15 +#> 25 10 19 +#> 26 10 8 +#> 27 12 9 +#> 28 6 14 +#> 29 14 10 +#> 30 11 18

We only need the counts of cases in each primary sampling unit:

-table(svy$psu, svy$stunted)[,1]
-#>  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
-#>  8 11  7  6  7 11 11 14 12 10 12  9  9  5 12  8  6  8 10  6 11 11 12  8 10 10 
-#> 27 28 29 30 
-#> 12  6 14 11
-barplot(table(svy$psu, svy$stunted)[,1], xlab = "PSU", ylab = "Cases", cex.names = 0.5)
+table(svy$psu, svy$stunted)[,1] +#> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 +#> 8 11 7 6 7 11 11 14 12 10 12 9 9 5 12 8 6 8 10 6 11 11 12 8 10 10 +#> 27 28 29 30 +#> 12 6 14 11 +barplot(table(svy$psu, svy$stunted)[,1], xlab = "PSU", ylab = "Cases", cex.names = 0.5)

It will be useful to keep this for later use:

-casesPerPSU <- table(svy$psu, svy$stunted)[,1] 
-casesPerPSU
-#>  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
-#>  8 11  7  6  7 11 11 14 12 10 12  9  9  5 12  8  6  8 10  6 11 11 12  8 10 10 
-#> 27 28 29 30 
-#> 12  6 14 11
-

We are interested in how cases are distributed across the primary sampling units.

-

There are three general patterns. These are random, clumped, and uniform.

-

We can identify the pattern to which the example data most likely belongs using an index of dispersion.

-

The simplest index of dispersion, and the one used by SMART, is the variance to mean ratio:

-

\[ \text{Variance to mean ratio} ~ = ~ \frac{s ^ 2}{\overline{\chi}} \]

-

The interpretation of the variance to mean ratio is straightforward:

+casesPerPSU <- table(svy$psu, svy$stunted)[,1] +casesPerPSU +#> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 +#> 8 11 7 6 7 11 11 14 12 10 12 9 9 5 12 8 6 8 10 6 11 11 12 8 10 10 +#> 27 28 29 30 +#> 12 6 14 11 +

We are interested in how cases are distributed across the primary +sampling units.

+

There are three general patterns. These are random, +clumped, and uniform.

+

We can identify the pattern to which the example data most likely +belongs using an index of dispersion.

+

The simplest index of dispersion, and the one used by SMART, is the +variance to mean ratio:

+

\[ \text{Variance to mean ratio} ~ = ~ +\frac{s ^ 2}{\overline{\chi}} \]

+

The interpretation of the variance to mean ratio is +straightforward:

Variance to mean ratio ≈ 1 Random

-

Variance to mean ratio > 1 Clumped (i.e. more clumped than random)

-

Variance to mean ratio < 1 Uniform (i.e. more uniform than random)

-

The value of the variance to mean ratio can range between zero (maximum uniformity) and the total number of cases in the data (maximum clumping). Maximum uniformity is found when the same number of cases are found in every primary sampling unit. Maximum clumping is found when all cases are found in one primary sampling unit.

+

Variance to mean ratio > 1 Clumped (i.e. more clumped than +random)

+

Variance to mean ratio < 1 Uniform (i.e. more uniform than +random)

+

The value of the variance to mean ratio can range between zero +(maximum uniformity) and the total number of cases in the data (maximum +clumping). Maximum uniformity is found when the same number of cases are +found in every primary sampling unit. Maximum clumping is found when all +cases are found in one primary sampling unit.

With the example data:

-varianceCasesPerPSU <- var(casesPerPSU)
-meanCasesPerPSU <- sum(casesPerPSU) / length(casesPerPSU) 
-V2M <- varianceCasesPerPSU / meanCasesPerPSU
-V2M
-#> [1] 0.6393127
-

The observed variance to mean ratio (0.6393127) suggests that the distribution of cases across primary sampling units is not completely uniform, but neither is it random.

-

A formal (Chi-squared) test can be performed. The Chi-squared test statistic can be calculated using:

+varianceCasesPerPSU <- var(casesPerPSU) +meanCasesPerPSU <- sum(casesPerPSU) / length(casesPerPSU) +V2M <- varianceCasesPerPSU / meanCasesPerPSU +V2M +#> [1] 0.6393127 +

The observed variance to mean ratio (0.6393127) +suggests that the distribution of cases across primary sampling units is +not completely uniform, but neither is it random.

+

A formal (Chi-squared) test can be performed. The +Chi-squared test statistic can be calculated using:

-sum((casesPerPSU - meanCasesPerPSU)^2) / meanCasesPerPSU
+sum((casesPerPSU - meanCasesPerPSU)^2) / meanCasesPerPSU

This returns:

-
#> [1] 18.54007
+
#> [1] 18.54007

18.54007

The critical values for this test statistic can be found using:

-qchisq(p = c(0.025, 0.975), df = length(casesPerPSU) - 1)
+qchisq(p = c(0.025, 0.975), df = length(casesPerPSU) - 1)

This returns:

-qchisq(p = c(0.025, 0.975), df = length(casesPerPSU) - 1)
+qchisq(p = c(0.025, 0.975), df = length(casesPerPSU) - 1)

16.04707 45.72229

-

If the Chi-squared test statistic was below 16.04707 then we would conclude that the pattern of cases across primary sampling units in the example data is uniform. This is not the case in the example data.

-

If the Chi-squared test statistic was above 45.72229 then we would conclude that the pattern of cases across primary sampling units in the example data is clumped. This is not the case in the example data.

-

Since the Chi-squared test statistic falls between 16.04707 and 45.72229 we conclude that the pattern of cases across primary sampling units in the example data is random.

-

There are problems with the variance to mean ratio. Some clearly non-random patterns can produce variance to mean ratios of one. The variance to mean ratio is also strongly influenced by the total number of cases present in the data when clumping is present.

+

If the Chi-squared test statistic was below 16.04707 +then we would conclude that the pattern of cases across primary sampling +units in the example data is uniform. This is not the case in the +example data.

+

If the Chi-squared test statistic was above 45.72229 +then we would conclude that the pattern of cases across primary sampling +units in the example data is clumped. This is not the case in the +example data.

+

Since the Chi-squared test statistic falls between +16.04707 and 45.72229 we conclude that +the pattern of cases across primary sampling units in the example data +is random.

+

There are problems with the variance to mean ratio. Some clearly +non-random patterns can produce variance to mean ratios of one. The +variance to mean ratio is also strongly influenced by the total number +of cases present in the data when clumping is present.

A better measure is Green’s Index of Dispersion:

-

\[ \text{Green's Index} ~ = ~ \frac{ \left ( \frac{s ^ 2}{\overline{\chi}} \right ) ~ - ~ 1}{n ~ - ~ 1} \]

-

Green’s Index corrects the variance to mean ratio for the total number of cases present in the data.

-

The value of Green’s Index can range between $ -1 / (n - 1) $ for maximum uniformity (specific to the dataset) and one for maximum clumping. The interpretation of Green’s Index is straightforward:

+

\[ \text{Green's Index} ~ = ~ \frac{ +\left ( \frac{s ^ 2}{\overline{\chi}} \right ) ~ - ~ 1}{n ~ - ~ 1} +\]

+

Green’s Index corrects the variance to mean ratio for the total +number of cases present in the data.

+

The value of Green’s Index can range between $ -1 / (n - 1) $ for +maximum uniformity (specific to the dataset) and one for maximum +clumping. The interpretation of Green’s Index is straightforward:

Green’s Index ≈ 0 Random

Green’s Index > 0 Clumped (i.e. more clumped than random)

Green’s Index < 0 Uniform (i.e. more uniform than random)

-

The sampling distribution of Green’s Index is not well described. The NiPN data quality toolkit provides the greenIndex() function that overcomes this problem. This R language function uses the bootstrap technique to estimate Green’s Index and test whether the distribution of cases across primary sampling units is random.

-

The greenIndex() function requires you to specify the name of the survey dataset, the name of the variable specifying the primary sampling unit, and the name of the variable specifying case status. With the example data:

+

The sampling distribution of Green’s Index is not well described. The +NiPN data quality toolkit provides the greenIndex() +function that overcomes this problem. This R language function uses the +bootstrap technique to estimate Green’s Index and test whether the +distribution of cases across primary sampling units is random.

+

The greenIndex() function requires you to specify the +name of the survey dataset, the name of the variable specifying the +primary sampling unit, and the name of the variable specifying case +status. With the example data:

-greensIndex(data = svy, psu = "psu", case = "stunted")
+greensIndex(data = svy, psu = "psu", case = "stunted")

this returns:

-
#> 
-#>  Green's Index of Dispersion
-#> 
-#> Green's Index (GI) of Dispersion  = -0.0013, 95% CI = (-0.0022, -0.0004)
-#> Maximum uniformity for this data  = -0.0035
-#>                          p-value  =  0.0000
-

The point estimate of Green’s Index (-0.0013) is below zero and the p-value of the test for a random distribution of cases across primary sampling units (0.0040) is below 0.05. The distribution of cases across primary sampling units in the example data is significantly more uniform than it is random. We can see this graphically using:

+
#> 
+#>  Green's Index of Dispersion
+#> 
+#> Green's Index (GI) of Dispersion  = -0.0013, 95% CI = (-0.0022, -0.0004)
+#> Maximum uniformity for this data  = -0.0035
+#>                          p-value  =  0.0000
+

The point estimate of Green’s Index (-0.0013) is +below zero and the p-value of the test for a random distribution of +cases across primary sampling units (0.0040) is below +0.05. The distribution of cases across primary sampling units in the +example data is significantly more uniform than it is random. We can see +this graphically using:

-table(svy$psu, svy$stunted)[,1]
-#>  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 
-#>  8 11  7  6  7 11 11 14 12 10 12  9  9  5 12  8  6  8 10  6 11 11 12  8 10 10 
-#> 27 28 29 30 
-#> 12  6 14 11
-barplot(table(svy$psu, svy$stunted)[,1], xlab = "PSU", ylab = "Cases", cex.names = 0.5)
-abline(h = sum(casesPerPSU) / length(casesPerPSU), lty = 2)
+table(svy$psu, svy$stunted)[,1] +#> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 +#> 8 11 7 6 7 11 11 14 12 10 12 9 9 5 12 8 6 8 10 6 11 11 12 8 10 10 +#> 27 28 29 30 +#> 12 6 14 11 +barplot(table(svy$psu, svy$stunted)[,1], xlab = "PSU", ylab = "Cases", cex.names = 0.5) +abline(h = sum(casesPerPSU) / length(casesPerPSU), lty = 2)

-

The dashed line on the plot marks the mean number of cases found in each primary sampling unit. A uniform distribution would show all bars ending close to this line (see figure above).

-

SMART uses the variance to mean ratio as a test of data quality. Green’s Index is a more robust choice because it can be used to compare samples that vary in overall sample size and the number of sampling units used.

-

The idea behind using a measure of dispersion to judge data quality is a belief that the distribution of cases of malnutrition across primary sampling units should always be random. If this is not the case then the data are considered to be suspect. The problem with this approach is that deviations from random can reflect the true distribution of cases in the survey area. This may occur when the survey area comprises, for example, more than one livelihood zone. It is also less likely to be the case for conditions, such as wasting and oedema, that are associated with infectious disease and so may be more clumped than randomly distributed across primary sampling units. This may become a particular problem when proximity sampling is used to collect the within-cluster samples.

-

Measures of dispersion are problematic when used as measures of data quality and should be interpreted with caution. The exception to this rule is finding maximum, or almost maximum, uniformity or maximum, or almost maximum, clumping. A finding of maximum uniformity is likely only when data have been fabricated. A finding of maximum clumping may indicate poor data collection and / or poor data management.

+

The dashed line on the plot marks the mean number of cases found in +each primary sampling unit. A uniform distribution would show all bars +ending close to this line (see figure above).

+

SMART uses the variance to mean ratio as a test of data quality. +Green’s Index is a more robust choice because it can be used to compare +samples that vary in overall sample size and the number of sampling +units used.

+

The idea behind using a measure of dispersion to judge data quality +is a belief that the distribution of cases of malnutrition across +primary sampling units should always be random. If this is not the case +then the data are considered to be suspect. The problem with this +approach is that deviations from random can reflect the true +distribution of cases in the survey area. This may occur when the survey +area comprises, for example, more than one livelihood zone. It is also +less likely to be the case for conditions, such as wasting and oedema, +that are associated with infectious disease and so may be more clumped +than randomly distributed across primary sampling units. This may become +a particular problem when proximity sampling is used to collect the +within-cluster samples.

+

Measures of dispersion are problematic when used as measures of data +quality and should be interpreted with caution. The exception to this +rule is finding maximum, or almost maximum, uniformity or maximum, or +almost maximum, clumping. A finding of maximum uniformity is likely only +when data have been fabricated. A finding of maximum clumping may +indicate poor data collection and / or poor data management.

- - - - + -
+ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-102-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-102-1.png index 5352a12..f5b4c03 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-102-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-102-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-11-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-11-1.png index 6be6acb..ab2fb1a 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-11-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-11-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-13-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-13-1.png index 09df8c4..075d268 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-13-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-13-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-15-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-15-1.png index 3a85492..dacc934 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-15-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-15-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-15-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-15-2.png index e682e4e..1f70c08 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-15-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-15-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-16-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-16-1.png index bb8d28e..8d30f81 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-16-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-16-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-20-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-20-1.png index 81f7ae7..3449b34 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-20-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-20-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-20-2.png index e0cc981..ddd1bc2 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-20-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-20-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-21-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-21-1.png index c65505d..df3f13c 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-21-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-21-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-21-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-21-2.png index f9d5327..7881fe7 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-21-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-21-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-22-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-22-1.png index db46d94..5fcf7e7 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-22-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-23-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-23-1.png index db46d94..5fcf7e7 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-23-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-23-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-24-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-24-1.png index 2924089..e0b566b 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-24-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-24-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-25-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-25-1.png index 1418742..b421a96 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-25-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-25-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-27-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-27-1.png index 1bea09d..6a6d465 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-27-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-27-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-27-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-27-2.png index 1e6925f..bed73e3 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-27-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-27-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-28-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-28-1.png index 40ad74d..cc514a6 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-28-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-28-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-28-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-28-2.png index 7cb49de..23da687 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-28-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-28-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-29-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-29-1.png index d167d6e..e6b42de 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-29-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-29-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-30-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-30-1.png index aa13905..83da586 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-30-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-30-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-32-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-32-1.png index 676c2b6..d1a1363 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-32-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-32-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-32-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-32-2.png index 16c969f..dd54d22 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-32-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-32-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-33-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-33-1.png index 7be0180..a9d4da6 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-33-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-33-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-33-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-33-2.png index aa13905..83da586 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-33-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-33-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-37-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-37-1.png index af0bc6a..12b6e6f 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-37-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-37-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-37-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-37-2.png index 6e67b24..6c02245 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-37-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-37-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-43-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-43-1.png index d167d6e..e6b42de 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-43-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-43-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-48-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-48-1.png index ca53de6..3aca9ee 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-48-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-48-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-52-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-52-1.png index baf30c9..bb1a58e 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-52-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-52-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-52-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-52-2.png index 54bddbb..7a322b9 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-52-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-52-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-55-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-55-1.png index f1b0e8c..87d6cb2 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-55-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-55-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-55-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-55-2.png index a0c676a..2cba465 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-55-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-55-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-58-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-58-1.png index e65421a..949561a 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-58-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-58-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-58-2.png b/docs/articles/ad_files/figure-html/unnamed-chunk-58-2.png index 4272c0a..d4da369 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-58-2.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-58-2.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-61-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-61-1.png index 3f34275..69aff83 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-61-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-61-1.png differ diff --git a/docs/articles/ad_files/figure-html/unnamed-chunk-93-1.png b/docs/articles/ad_files/figure-html/unnamed-chunk-93-1.png index c3a65bb..2367c9d 100644 Binary files a/docs/articles/ad_files/figure-html/unnamed-chunk-93-1.png and b/docs/articles/ad_files/figure-html/unnamed-chunk-93-1.png differ diff --git a/docs/articles/ah.html b/docs/articles/ah.html index cc4c8c8..449d6da 100644 --- a/docs/articles/ah.html +++ b/docs/articles/ah.html @@ -4,7 +4,8 @@ - + + Age heaping • nipnTK @@ -12,362 +13,388 @@ - - - + + + - - + + - -
-
-
-
-

This returns:

-
#>   psu age sex weight height muac oedema
-#> 1   1   6   1    7.3   65.0  146      2
-#> 2   1  42   2   12.5   89.5  156      2
-#> 3   1  23   1   10.6   78.1  149      2
-#> 4   1  18   1   12.8   81.5  160      2
-#> 5   1  52   1   12.1   87.3  152      2
-#> 6   1  36   2   16.9   93.0  190      2
-
-

-Summarising, tabulating, and visualising age data

+
#>   psu age sex weight height muac oedema
+#> 1   1   6   1    7.3   65.0  146      2
+#> 2   1  42   2   12.5   89.5  156      2
+#> 3   1  23   1   10.6   78.1  149      2
+#> 4   1  18   1   12.8   81.5  160      2
+#> 5   1  52   1   12.1   87.3  152      2
+#> 6   1  36   2   16.9   93.0  190      2
+
+

Summarising, tabulating, and visualising age data +

The variable of interest is age (age in months):

-summary(svy$age)
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>     6.0    18.0    30.0    30.4    42.0    59.0
-

Tables can be difficult to use with ungrouped age data because there are usually many different values:

+summary(svy$age) +#> Min. 1st Qu. Median Mean 3rd Qu. Max. +#> 6.0 18.0 30.0 30.4 42.0 59.0
+

Tables can be difficult to use with ungrouped age data because there +are usually many different values:

-table(svy$age)
-#> 
-#>  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
-#> 10 17 25 13 19 23 38 11 11 17  9 14 26  9 17 14 24 12 31  8 13  9 21 14 38 14 
-#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 
-#> 16 23 22 18 57  8 13  9 11 12 19 10 13 14 12 14 44  6  9  6  5  8 12 13 12  8 
-#> 58 59 
-#> 13  9
-

The fullTable() function from the NiPN data-quality toolkit is preferred as this will include values with zero counts:

+table(svy$age) +#> +#> 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +#> 10 17 25 13 19 23 38 11 11 17 9 14 26 9 17 14 24 12 31 8 13 9 21 14 38 14 +#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 +#> 16 23 22 18 57 8 13 9 11 12 19 10 13 14 12 14 44 6 9 6 5 8 12 13 12 8 +#> 58 59 +#> 13 9
+

The fullTable() function from the NiPN data-quality +toolkit is preferred as this will include values with zero counts:

-fullTable(svy$age, values = 6:59)
-#>  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
-#> 10 17 25 13 19 23 38 11 11 17  9 14 26  9 17 14 24 12 31  8 13  9 21 14 38 14 
-#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 
-#> 16 23 22 18 57  8 13  9 11 12 19 10 13 14 12 14 44  6  9  6  5  8 12 13 12  8 
-#> 58 59 
-#> 13  9
-

We used the fullTable() function here because it returns a table containing cells for every value specified by the values parameter. The returned table will also only contain cells for the values specified by the values parameter. The default for the values parameter is the range of the variable being tabulated. This means that the values parameter can be sometimes be omitted:

+fullTable(svy$age, values = 6:59) +#> 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +#> 10 17 25 13 19 23 38 11 11 17 9 14 26 9 17 14 24 12 31 8 13 9 21 14 38 14 +#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 +#> 16 23 22 18 57 8 13 9 11 12 19 10 13 14 12 14 44 6 9 6 5 8 12 13 12 8 +#> 58 59 +#> 13 9
+

We used the fullTable() function here because it returns +a table containing cells for every value specified by the +values parameter. The returned table will also only +contain cells for the values specified by the values +parameter. The default for the values parameter is the +range of the variable being tabulated. This means that the values +parameter can be sometimes be omitted:

-fullTable(svy$age)
-#>  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
-#> 10 17 25 13 19 23 38 11 11 17  9 14 26  9 17 14 24 12 31  8 13  9 21 14 38 14 
-#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 
-#> 16 23 22 18 57  8 13  9 11 12 19 10 13 14 12 14 44  6  9  6  5  8 12 13 12  8 
-#> 58 59 
-#> 13  9
-

Omitting the values parameter only works reliably for numeric variables containing whole numbers. If the variable being tabulated is a character variable or is a numeric variable containing one or more numbers with decimal places then you should specify the values parameter.

-

A graphical analysis is usually more informative than a tabular analysis:

+fullTable(svy$age) +#> 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +#> 10 17 25 13 19 23 38 11 11 17 9 14 26 9 17 14 24 12 31 8 13 9 21 14 38 14 +#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 +#> 16 23 22 18 57 8 13 9 11 12 19 10 13 14 12 14 44 6 9 6 5 8 12 13 12 8 +#> 58 59 +#> 13 9
+

Omitting the values parameter only works reliably +for numeric variables containing whole numbers. If the variable being +tabulated is a character variable or is a numeric variable containing +one or more numbers with decimal places then you should specify the +values parameter.

+

A graphical analysis is usually more informative than a tabular +analysis:

-barplot(fullTable(svy$age, values = 6:59), 
-        xlab = "Age (months)", ylab = "Frequency", las = 3, cex.names = 0.6)
+barplot(fullTable(svy$age, values = 6:59), + xlab = "Age (months)", ylab = "Frequency", las = 3, cex.names = 0.6)

-

We expect all ages to be present in roughly equal frequency or with frequency reducing slowly with age due to mortality. We can see that there is marked age-heaping at 12, 18, 24, 30, 36, and 48 months (see figure above). This is very common when age is reported by mothers. This is because of a tendency for mothers and other carers to round ages to whole years or half years.

-

Note that we used values = 6:59 with the fullTable() function from the NiPN data quality toolkit. We did this because it is the range of values that should be present in the age variable.

+

We expect all ages to be present in roughly equal frequency or with +frequency reducing slowly with age due to mortality. We can see that +there is marked age-heaping at 12, 18, 24, 30, 36, and 48 months (see +figure above). This is very common when age is reported by mothers. This +is because of a tendency for mothers and other carers to round ages to +whole years or half years.

+

Note that we used values = 6:59 with the +fullTable() function from the NiPN data quality toolkit. We +did this because it is the range of values that should be present in the +age variable.

-
-

-Age heaping in children

-

Age heaping can seriously affect survey results for indices that include an age component (e.g. height- for-age and weight-for age). The effect is important when there is systematic rounding up or systematic rounding down. Systematic rounding can lead to bias. If rounding is systematically down then indices will be biased upwards and prevalence biased downwards. If rounding is systematically up then indices will be biased downwards and prevalence biased upwards.

-

A useful way of looking at age heaping when age is recorded in months is to examine the remainders when the ages are divided by 12.

-

The R language provides a special operator (%%) to help with this:

+
+

Age heaping in children +

+

Age heaping can seriously affect survey results for indices that +include an age component (e.g. height- for-age and weight-for age). The +effect is important when there is systematic rounding up or systematic +rounding down. Systematic rounding can lead to bias. If rounding is +systematically down then indices will be biased upwards and prevalence +biased downwards. If rounding is systematically up then indices will be +biased downwards and prevalence biased upwards.

+

A useful way of looking at age heaping when age is recorded in months +is to examine the remainders when the ages are divided by 12.

+

The R language provides a special operator (%%) to help with +this:

-rem <- svy$age %% 12
-remTable <- fullTable(rem, values = 0:11)
-remTable
-#>   0   1   2   3   4   5   6   7   8   9  10  11 
-#> 170  33  46  41  46  48 105  63  83  72  90  76
-prop.table(remTable) * 100
-#>         0         1         2         3         4         5         6         7 
-#> 19.473081  3.780069  5.269187  4.696449  5.269187  5.498282 12.027491  7.216495 
-#>         8         9        10        11 
-#>  9.507446  8.247423 10.309278  8.705613
-barplot(remTable, xlab = "Age (months) %% 12", ylab = "Frequency") 
-abline(h = sum(remTable / 12), lty = 3)
+rem <- svy$age %% 12 +remTable <- fullTable(rem, values = 0:11) +remTable +#> 0 1 2 3 4 5 6 7 8 9 10 11 +#> 170 33 46 41 46 48 105 63 83 72 90 76 +prop.table(remTable) * 100 +#> 0 1 2 3 4 5 6 7 +#> 19.473081 3.780069 5.269187 4.696449 5.269187 5.498282 12.027491 7.216495 +#> 8 9 10 11 +#> 9.507446 8.247423 10.309278 8.705613 +barplot(remTable, xlab = "Age (months) %% 12", ylab = "Frequency") +abline(h = sum(remTable / 12), lty = 3)

-chisq.test(remTable)
-#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  remTable
-#> X-squared = 214.96, df = 11, p-value < 2.2e-16
-

The NiPN data quality toolkit provides an R language function called ageHeaping() that performs this age-heaping analysis. Applying this function to the example data:

+chisq.test(remTable) +#> +#> Chi-squared test for given probabilities +#> +#> data: remTable +#> X-squared = 214.96, df = 11, p-value < 2.2e-16
+

The NiPN data quality toolkit provides an R language function called +ageHeaping() that performs this age-heaping analysis. +Applying this function to the example data:

-ageHeaping(svy$age)
+ageHeaping(svy$age)

This returns:

-
#> 
-#>  Age-heaping Analysis
-#> 
-#> data:    Remainder of svy$age / 12
-#> X-squared = 214.9588, df = 11, p-value = 0.0000
-

The output of the ageHeaping() function can be saved for later use:

+
#> 
+#>  Age-heaping Analysis
+#> 
+#> data:    Remainder of svy$age / 12
+#> X-squared = 214.9588, df = 11, p-value = 0.0000
+

The output of the ageHeaping() function can be saved for +later use:

-ah12 <- ageHeaping(svy$age)
-

The saved output contains the Chi-squared test and frequency tables of the final digits (counts and percentages). These can be accessed using:

+ah12 <- ageHeaping(svy$age) +

The saved output contains the Chi-squared test and frequency tables +of the final digits (counts and percentages). These can be accessed +using:

-ah12
-#> 
-#>  Age-heaping Analysis
-#> 
-#> data:    Remainder of svy$age / 12
-#> X-squared = 214.9588, df = 11, p-value = 0.0000
-ah12$X2
-#> X-squared 
-#>  214.9588
-ah12$df
-#> df 
-#> 11
-ah12$p
-#> [1] 5.791598e-40
-ah12$tab
-#> Remainder of svy$age / 12
-#>   0   1   2   3   4   5   6   7   8   9  10  11 
-#> 170  33  46  41  46  48 105  63  83  72  90  76
-ah12$pct
-#> Remainder of svy$age / 12
-#>    0    1    2    3    4    5    6    7    8    9   10   11 
-#> 19.5  3.8  5.3  4.7  5.3  5.5 12.0  7.2  9.5  8.2 10.3  8.7
+ah12 +#> +#> Age-heaping Analysis +#> +#> data: Remainder of svy$age / 12 +#> X-squared = 214.9588, df = 11, p-value = 0.0000 +ah12$X2 +#> X-squared +#> 214.9588 +ah12$df +#> df +#> 11 +ah12$p +#> [1] 5.791598e-40 +ah12$tab +#> Remainder of svy$age / 12 +#> 0 1 2 3 4 5 6 7 8 9 10 11 +#> 170 33 46 41 46 48 105 63 83 72 90 76 +ah12$pct +#> Remainder of svy$age / 12 +#> 0 1 2 3 4 5 6 7 8 9 10 11 +#> 19.5 3.8 5.3 4.7 5.3 5.5 12.0 7.2 9.5 8.2 10.3 8.7

The saved results may also be plotted:

-plot(ah12, main = "Age-heaping (remainder of age / 12)")
+plot(ah12, main = "Age-heaping (remainder of age / 12)")

The resulting plot is shown below.

-

The ageHeaping() function assumes that you want to examine the remainder after dividing by twelve. This is useful when working with ages that are recorded in months. It may also be useful to use other divisors, such as examining the remainder after dividing by six:

+

The ageHeaping() function assumes that you want to +examine the remainder after dividing by twelve. This is useful when +working with ages that are recorded in months. It may also be useful to +use other divisors, such as examining the remainder after dividing by +six:

-ah6 <- ageHeaping(svy$age, divisor = 6) 
-print(ah6)
-#> 
-#>  Age-heaping Analysis
-#> 
-#> data:    Remainder of svy$age / 6
-#> X-squared = 145.0275, df = 5, p-value = 0.0000
-plot(ah6)
+ah6 <- ageHeaping(svy$age, divisor = 6) +print(ah6) +#> +#> Age-heaping Analysis +#> +#> data: Remainder of svy$age / 6 +#> X-squared = 145.0275, df = 5, p-value = 0.0000 +plot(ah6)

-

This shows the extent of age heaping at whole and half-years (see figure above).

+

This shows the extent of age heaping at whole and half-years (see +figure above).

-
-

-Age heaping in adults

-

Using ten and five as divisors can be useful when dealing with data for adults in which ages are recorded in whole years. For example:

+
+

Age heaping in adults +

+

Using ten and five as divisors can be useful when dealing with data +for adults in which ages are recorded in whole years. For example:

-svy <- read.table("ah.ex01.csv", header = TRUE, sep = ",") 
-head(svy)
+svy <- read.table("ah.ex01.csv", header = TRUE, sep = ",") +head(svy)
-svy <- ah.ex01 
-head(svy)
-

The file ah.ex01.csv is a comma-separated-value (CSV) file containing anthropometric data from a Rapid Assessment Method for Older People (RAM-OP) survey in the Dadaab refugee camp in Garissa, Kenya. This is a survey of people aged sixty years and older.

+svy <- ah.ex01 +head(svy)
+

The file ah.ex01.csv is a comma-separated-value +(CSV) file containing anthropometric data from a Rapid Assessment Method +for Older People (RAM-OP) survey in the Dadaab refugee camp in Garissa, +Kenya. This is a survey of people aged sixty years and older.

The variable of interest is age (age in years):

-summary(svy$age)
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>     6.0    18.0    30.0    30.4    42.0    59.0
-

Care should be exercised when specifying the divisor to use in the analysis of age heaping. Not all calendars use base ten. Amongst Han Chinese, for example, age heaping may occur with a twelve-year cycle corresponding to preferred animal years in the Chinese calendar. An analysis of age heaping that concentrates on specific digits (e.g. zero and five) or on decimal intervals will not be appropriate in all populations. It is advisable, therefore to use simple tabulation and visualisation techniques to heap decide an appropriate divisor.

+summary(svy$age) +#> Min. 1st Qu. Median Mean 3rd Qu. Max. +#> 6.0 18.0 30.0 30.4 42.0 59.0 +

Care should be exercised when specifying the divisor +to use in the analysis of age heaping. Not all calendars use base ten. +Amongst Han Chinese, for example, age heaping may occur with a +twelve-year cycle corresponding to preferred animal years in the Chinese +calendar. An analysis of age heaping that concentrates on specific +digits (e.g. zero and five) or on decimal intervals will not be +appropriate in all populations. It is advisable, therefore to use simple +tabulation and visualisation techniques to heap decide an appropriate +divisor.

With the example data:

-summary(svy$age)
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>     6.0    18.0    30.0    30.4    42.0    59.0
-fullTable(svy$age)
-#>  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
-#> 10 17 25 13 19 23 38 11 11 17  9 14 26  9 17 14 24 12 31  8 13  9 21 14 38 14 
-#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 
-#> 16 23 22 18 57  8 13  9 11 12 19 10 13 14 12 14 44  6  9  6  5  8 12 13 12  8 
-#> 58 59 
-#> 13  9
-barplot(fullTable(svy$age), 
-        xlab = "Age (years)", ylab = "Frequency", las = 3, cex.names = 0.6)
+summary(svy$age) +#> Min. 1st Qu. Median Mean 3rd Qu. Max. +#> 6.0 18.0 30.0 30.4 42.0 59.0 +fullTable(svy$age) +#> 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 +#> 10 17 25 13 19 23 38 11 11 17 9 14 26 9 17 14 24 12 31 8 13 9 21 14 38 14 +#> 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 +#> 16 23 22 18 57 8 13 9 11 12 19 10 13 14 12 14 44 6 9 6 5 8 12 13 12 8 +#> 58 59 +#> 13 9 +barplot(fullTable(svy$age), + xlab = "Age (years)", ylab = "Frequency", las = 3, cex.names = 0.6)

-

shows age-heaping at decades and half-decades (see figure above). In this survey using a divisor of 10 would be appropriate:

+

shows age-heaping at decades and half-decades (see figure above). In +this survey using a divisor of 10 would be +appropriate:

-ah10 <- ageHeaping(svy$age, divisor = 10) 
-print(ah10)
-#> 
-#>  Age-heaping Analysis
-#> 
-#> data:    Remainder of svy$age / 10
-#> X-squared = 70.31042, df = 9, p-value = 0.0000
-plot(ah10)
+ah10 <- ageHeaping(svy$age, divisor = 10) +print(ah10) +#> +#> Age-heaping Analysis +#> +#> data: Remainder of svy$age / 10 +#> X-squared = 70.31042, df = 9, p-value = 0.0000 +plot(ah10)

-

There is pronounced age heaping at decades and, to a lesser extent, half-decades in these data (see figure above).

-

It may also be useful to use other divisors, such as examining the remainder after dividing by five:

+

There is pronounced age heaping at decades and, to a lesser extent, +half-decades in these data (see figure above).

+

It may also be useful to use other divisors, such as examining the +remainder after dividing by five:

-ah5 <- ageHeaping(svy$age, divisor = 5) 
-print(ah5)
-#> 
-#>  Age-heaping Analysis
-#> 
-#> data:    Remainder of svy$age / 5
-#> X-squared = 10.39633, df = 4, p-value = 0.0343
-plot(ah5)
+ah5 <- ageHeaping(svy$age, divisor = 5) +print(ah5) +#> +#> Age-heaping Analysis +#> +#> data: Remainder of svy$age / 5 +#> X-squared = 10.39633, df = 4, p-value = 0.0343 +plot(ah5)

-

This shows the extent of age heaping at whole and half decades (see figure above).

+

This shows the extent of age heaping at whole and half decades (see +figure above).

- - - - + -
+ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-10-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-10-1.png index ae19e3f..38a1395 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-10-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-10-1.png differ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-11-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-11-1.png index 74e0ab6..3f998e0 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-11-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-11-1.png differ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-17-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-17-1.png index 8ac8d8e..ff0404c 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-17-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-18-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-18-1.png index 0ed8697..2416bec 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-18-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-18-1.png differ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-22-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-22-1.png index d0b9f22..10fe7ac 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-22-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-23-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-23-1.png index 1bcb193..42794b3 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-23-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-23-1.png differ diff --git a/docs/articles/ah_files/figure-html/unnamed-chunk-24-1.png b/docs/articles/ah_files/figure-html/unnamed-chunk-24-1.png index c433c67..ec5ceda 100644 Binary files a/docs/articles/ah_files/figure-html/unnamed-chunk-24-1.png and b/docs/articles/ah_files/figure-html/unnamed-chunk-24-1.png differ diff --git a/docs/articles/as.html b/docs/articles/as.html index 530fca1..3bd7b42 100644 --- a/docs/articles/as.html +++ b/docs/articles/as.html @@ -4,7 +4,8 @@ - + + Age and sex distributions • nipnTK @@ -12,464 +13,535 @@ - - - + + + - - + + - -
-
- +
-
-

This returns:

-
#>    
-#>             1         2
-#>   1 23.059361 24.367816
-#>   2 23.287671 22.068966
-#>   3 28.767123 26.436782
-#>   4 17.808219 18.850575
-#>   5  7.077626  8.275862
-

We expect there to be approximately equal proportions of children in the age-groups centred at 1, 2, 3, and 4 years and a smaller proportion (i.e. about half that in the other age-groups) in the age-group centred at 5 years. We specified margin = 2 with the prop.table() function because we wanted column percentages.

-

A graphical analysis using a population pyramid can be useful. The NiPN data quality toolkit provides an R language function called pyramid.plot() for plotting population pyramids:

+
#>    
+#>             1         2
+#>   1 23.059361 24.367816
+#>   2 23.287671 22.068966
+#>   3 28.767123 26.436782
+#>   4 17.808219 18.850575
+#>   5  7.077626  8.275862
+

We expect there to be approximately equal proportions of children in +the age-groups centred at 1, 2, 3, and 4 years and a smaller proportion +(i.e. about half that in the other age-groups) in the age-group centred +at 5 years. We specified margin = 2 with the +prop.table() function because we wanted column +percentages.

+

A graphical analysis using a population pyramid can be useful. The +NiPN data quality toolkit provides an R language function +called pyramid.plot() for plotting population pyramids:

-pyramid.plot(svy$ycag, svy$sex)
+pyramid.plot(svy$ycag, svy$sex)

-

We can make a more informative plot by specifying a title and axis labels:

+

We can make a more informative plot by specifying a title and axis +labels:

-pyramid.plot(svy$ycag, svy$sex, 
-             main = "Distribution of age by sex",
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Year-centred age-group")
+pyramid.plot(svy$ycag, svy$sex, + main = "Distribution of age by sex", + xlab = "Frequency (Males | Females)", + ylab = "Year-centred age-group")

and applying shading:

-pyramid.plot(svy$ycag, svy$sex, 
-             main = "Distribution of age by sex",
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Year-centred age-group",
-             col = c("grey80", "white"))
+pyramid.plot(svy$ycag, svy$sex, + main = "Distribution of age by sex", + xlab = "Frequency (Males | Females)", + ylab = "Year-centred age-group", + col = c("grey80", "white"))

or colours:

-pyramid.plot(svy$ycag, svy$sex, 
-             main = "Distribution of age by sex",
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Year-centred age-group",
-             col = c("lightblue", "pink"))
+pyramid.plot(svy$ycag, svy$sex, + main = "Distribution of age by sex", + xlab = "Frequency (Males | Females)", + ylab = "Year-centred age-group", + col = c("lightblue", "pink"))

-

We expect there to be approximately equal numbers of children in the age-groups centred at 1, 2, 3, and 4 years and a smaller number (i.e. about half the number in the other age-groups) in the age-group centred at 5 years. There should also be approximately equal numbers of males and females. This is what we see in the population pyramid below.

+

We expect there to be approximately equal numbers of children in the +age-groups centred at 1, 2, 3, and 4 years and a smaller number +(i.e. about half the number in the other age-groups) in the age-group +centred at 5 years. There should also be approximately equal numbers of +males and females. This is what we see in the population pyramid +below.

-pyramid.plot(svy$ycag, svy$sex, 
-             main = "Distribution of age by sex",
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Year-centred age-group")
+pyramid.plot(svy$ycag, svy$sex, + main = "Distribution of age by sex", + xlab = "Frequency (Males | Females)", + ylab = "Year-centred age-group")

-

The pyramid.plot() function uses the values of the grouped age variable as y-axis value labels.

-

We can assign descriptive text values using the recode() function. For example:

+

The pyramid.plot() function uses the values of the +grouped age variable as y-axis value labels.

+

We can assign descriptive text values using the recode() +function. For example:

-svy$ageLabel <- recode(svy$age, "6:29='< 30 months'; 30:hi='30 month or older'")
-
-pyramid.plot(svy$ageLabel, 
-             svy$sex, 
-             main = "Distribution of age by sex", 
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Age-group")
+svy$ageLabel <- recode(svy$age, "6:29='< 30 months'; 30:hi='30 month or older'") +#> Warning in recode(svy$age, "6:29='< 30 months'; 30:hi='30 month or older'"): NAs +#> introduced by coercion + +pyramid.plot(svy$ageLabel, + svy$sex, + main = "Distribution of age by sex", + xlab = "Frequency (Males | Females)", + ylab = "Age-group")

-

We can also use a factor type variable. This type of variable allows labels to be specified:

+

We can also use a factor type variable. This type of variable allows +labels to be specified:

-svy$ageLabel <- factor(svy$ycag,
-                       labels = c("6:17", "18:29", "30:41", "42:53", "54:59"))
-
-pyramid.plot(svy$ageLabel, 
-             svy$sex, 
-             main = "Distribution of age by sex", 
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Year-centred age-group")
+svy$ageLabel <- factor(svy$ycag, + labels = c("6:17", "18:29", "30:41", "42:53", "54:59")) + +pyramid.plot(svy$ageLabel, + svy$sex, + main = "Distribution of age by sex", + xlab = "Frequency (Males | Females)", + ylab = "Year-centred age-group")

-

The cut() function may also be used:

+

The cut() function may also be used:

-svy$ageCuts <- cut(svy$age, breaks = c(0, 17, 29, 41, 53, 59))
-
-pyramid.plot(svy$ageCuts, 
-             svy$sex, 
-             main = "Age-group (months) ",
-             xlab = "Frequency (Males | Females)", 
-             ylab = "Year-centred age-group",
-             cex.names = 0.9)
+svy$ageCuts <- cut(svy$age, breaks = c(0, 17, 29, 41, 53, 59)) + +pyramid.plot(svy$ageCuts, + svy$sex, + main = "Age-group (months) ", + xlab = "Frequency (Males | Females)", + ylab = "Year-centred age-group", + cex.names = 0.9)

-

The cut() function is a versatile grouping function. It is explained in more detail later in this section.

-

The cex.names parameter of the pyramid.plot() function allows us to change the size of the value labels on the y-axis. The value for cex.names is a magnification factor. Values above one make the labels larger than the default. Values below one make the labels smaller than the default.

+

The cut() function is a versatile grouping function. It +is explained in more detail later in this section.

+

The cex.names parameter of the +pyramid.plot() function allows us to change the size of the +value labels on the y-axis. The value for cex.names is a +magnification factor. Values above one make the labels larger than the +default. Values below one make the labels smaller than the default.

-
-

-Simple testing

-

It is possible to perform a formal test on the distribution of age-groups by sex. A simple test is:

+
+

Simple testing +

+

It is possible to perform a formal test on the distribution of +age-groups by sex. A simple test is:

-chisq.test(table(svy$ycag, svy$sex))
+chisq.test(table(svy$ycag, svy$sex))

This yields:

-
#> 
-#>  Pearson's Chi-squared test
-#> 
-#> data:  table(svy$ycag, svy$sex)
-#> X-squared = 1.2675, df = 4, p-value = 0.8669
-

In this example the p-value is not below 0.05 so we accept the null hypothesis that there is no significant association between age and sex. This is an important test as it tests whether the distribution of ages is similar for males and females. It does not, however, test whether the age structure in the sample meets expectations. This requires a test that compares observed numbers with expected numbers derived from an external source (e.g. census data) or from a demographic model.

-
-

-A model of the expected age structure

-

A simple model-based method for calculating expected numbers is exponential decay in a population in which births and deaths balance each other and with a 1:1 male to female sex ratio. Under this model the proportion surviving in each group at each year can be calculated as:

+
#> 
+#>  Pearson's Chi-squared test
+#> 
+#> data:  table(svy$ycag, svy$sex)
+#> X-squared = 1.2675, df = 4, p-value = 0.8669
+

In this example the p-value is not below 0.05 so we accept the null +hypothesis that there is no significant association between age and sex. +This is an important test as it tests whether the distribution of ages +is similar for males and females. It does not, however, test whether the +age structure in the sample meets expectations. This requires a test +that compares observed numbers with expected numbers derived from an +external source (e.g. census data) or from a demographic model.

+
+

A model of the expected age structure +

+

A simple model-based method for calculating expected numbers is +exponential decay in a population in which births and deaths balance +each other and with a 1:1 male to female sex ratio. Under this model the +proportion surviving in each group at each year can be calculated +as:

\[ p ~ = ~ e ^ {-zt} \]

-

in which e is the base of the natural logarithm (approximately 2.7183), z is the mortality rate associated with each time period, and t is time. Time (t) starts at zero for the purposes of computation. Age can be used as a measure of time since birth. We should use 0 for the first year-centred age-group, 1 for the second year-centred age-group, and so-on. This is the rationale for us using t <- 0:4 below.

-

With five year-centred age-groups and a mortality rate of 1 / 10,000 / day, the expected proportions surviving at each year can be calculated as:

+

in which e is the base of the natural logarithm (approximately +2.7183), z is the mortality rate associated with each time period, and t +is time. Time (t) starts at zero for the purposes of computation. Age +can be used as a measure of time since birth. We should use 0 for the +first year-centred age-group, 1 for the second year-centred age-group, +and so-on. This is the rationale for us using t <- 0:4 +below.

+

With five year-centred age-groups and a mortality rate of 1 / 10,000 +/ day, the expected proportions surviving at each year can be calculated +as:

-z <- (1 / 10000) * 365.25 
-
-t <- 0:4
-
-p <- exp(-z * t)
-
-p
+z <- (1 / 10000) * 365.25 + +t <- 0:4 + +p <- exp(-z * t) + +p

This yields the following survival probabilities:

-z <- (1 / 10000) * 365.25 
-
-t <- 0:4
-
-p <- exp(-z * t)
-
-p
-#> [1] 1.0000000 0.9641340 0.9295544 0.8962149 0.8640713
-

We need to specify the duration (i.e. the number of years) represented by each age-group:

+z <- (1 / 10000) * 365.25 + +t <- 0:4 + +p <- exp(-z * t) + +p +#> [1] 1.0000000 0.9641340 0.9295544 0.8962149 0.8640713
+

We need to specify the duration (i.e. the number of years) +represented by each age-group:

-d <- c(1, 1, 1, 1, 0.5)
-

We can then calculate expected proportions of children in each age-group:

+d <- c(1, 1, 1, 1, 0.5)
+

We can then calculate expected proportions of children in each +age-group:

-ep <- d * p / sum(d * p) 
-
-ep
+ep <- d * p / sum(d * p) + +ep

This gives:

-
#> [1] 0.2368580 0.2283628 0.2201724 0.2122757 0.1023311
+
#> [1] 0.2368580 0.2283628 0.2201724 0.2122757 0.1023311

We can now calculate expected numbers:

-
expected <- ep * sum(table(svy$ycag)) names(expected) <- 1:5
-
-expected
+
expected <- ep * sum(table(svy$ycag)) names(expected) <- 1:5
+
+expected

giving:

-
#>         1         2         3         4         5 
-#> 206.77703 199.36076 192.21049 185.31667  89.33505
-

A formal test would compare the observed numbers with the expected numbers. The observed numbers can be found using:

+
#>         1         2         3         4         5 
+#> 206.77703 199.36076 192.21049 185.31667  89.33505
+

A formal test would compare the observed numbers with the expected +numbers. The observed numbers can be found using:

-observed <- table(svy$ycag) 
-
-observed
+observed <- table(svy$ycag) + +observed

This gives:

-
#> 
-#>   1   2   3   4   5 
-#> 207 198 241 160  67
-

It can be useful to examine observed and expected numbers graphically:

+
#> 
+#>   1   2   3   4   5 
+#> 207 198 241 160  67
+

It can be useful to examine observed and expected numbers +graphically:

-par(mfcol = c(1, 2))
-barplot(observed, main = "Observed", xlab = "Age group", ylab = "Frequency", ylim = c(0, 250))
-barplot(expected, main = "Expected", xlab = "Age group", ylab = "Frequency", ylim = c(0, 250))
+par(mfcol = c(1, 2)) +barplot(observed, main = "Observed", xlab = "Age group", ylab = "Frequency", ylim = c(0, 250)) +barplot(expected, main = "Expected", xlab = "Age group", ylab = "Frequency", ylim = c(0, 250))

We will calculate a Chi-squared test statistic:

-

\[ \chi ^ 2 ~ = ~ \sum \frac{(\text{observed} - \text{expected}) ^ 2}{\text{expected}} \]

+

\[ \chi ^ 2 ~ = ~ \sum +\frac{(\text{observed} - \text{expected}) ^ 2}{\text{expected}} +\]

using:

-X2 <- sum((observed - expected) ^ 2 / expected)
+X2 <- sum((observed - expected) ^ 2 / expected)

which yields a Chi-Squared test statistic of:

We can find the p-value using:

-pchisq(X2, df = 4, lower.tail = FALSE)
+pchisq(X2, df = 4, lower.tail = FALSE)

This gives:

-
#> [1] 0.000259395
-

In this example the age distribution is significantly different from expected numbers calculated using a simple demographic model.

-

Note that we specify the degrees of freedom (df) for the Chi-Squared test as the number of age-groups minus one. As we have five age-groups we specify df = 4. The degrees of freedom (df) that we need to specify will depend on the number of age-groups that we use. It is always the number of age-groups minus one. If, for example, there are ten age-groups we would need to specify df = 9.

-

The NiPN data quality toolkit provides an R function called ageChildren() that performs the model- based Chi-Squared test:

+
#> [1] 0.000259395
+

In this example the age distribution is significantly different from +expected numbers calculated using a simple demographic model.

+

Note that we specify the degrees of freedom (df) for the +Chi-Squared test as the number of age-groups minus one. As we have five +age-groups we specify df = 4. The degrees of freedom +(df) that we need to specify will depend on the number of +age-groups that we use. It is always the number of age-groups minus one. +If, for example, there are ten age-groups we would need to specify +df = 9.

+

The NiPN data quality toolkit provides an R function called +ageChildren() that performs the model- based Chi-Squared +test:

-ageChildren(svy$age, u5mr = 1)
+ageChildren(svy$age, u5mr = 1)

which returns:

-
#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 21.4366, df = 4, p = 0.0003
-

Note that we specified the under five years mortality rate as 1 / 10,000 / day using u5mr = 1. Another, more appropriate, rate may be specified.

-

The ageChildren() function calculates year-centred age-groups for children aged between six and fifty-nine months by default. This is a standard survey population and is used in SMART and many other surveys. The use of year-centred age-groups is also standard practice. The commands that are given above can, however, be adapted for use with different age-groups.

-

The output of the ageChildren() function can be saved for later use:

+
#> 
+#>  Age Test (Children)
+#> 
+#> X-squared = 21.4366, df = 4, p = 0.0003
+

Note that we specified the under five years mortality rate as 1 / +10,000 / day using u5mr = 1. Another, more appropriate, +rate may be specified.

+

The ageChildren() function calculates year-centred +age-groups for children aged between six and fifty-nine months by +default. This is a standard survey population and is used in SMART and +many other surveys. The use of year-centred age-groups is also standard +practice. The commands that are given above can, however, be adapted for +use with different age-groups.

+

The output of the ageChildren() function can be saved +for later use:

-ac <- ageChildren(svy$age, u5mr = 1)
-

The saved output contains the Chi-squared test results and tables of observed and expected values. These can be accessed using:

+ac <- ageChildren(svy$age, u5mr = 1) +

The saved output contains the Chi-squared test results and tables of +observed and expected values. These can be accessed using:

-ac
-#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 21.4366, df = 4, p = 0.0003
-
-ac$X2
-#> [1] 21.43662
-
-ac$df
-#> [1] 4
-
-ac$p 
-#> [1] 0.000259395
-
-ac$observed 
-#>   1   2   3   4   5 
-#> 207 198 241 160  67
-
-ac$expected
-#>         1         2         3         4         5 
-#> 206.77703 199.36076 192.21049 185.31667  89.33505
+ac +#> +#> Age Test (Children) +#> +#> X-squared = 21.4366, df = 4, p = 0.0003 + +ac$X2 +#> [1] 21.43662 + +ac$df +#> [1] 4 + +ac$p +#> [1] 0.000259395 + +ac$observed +#> 1 2 3 4 5 +#> 207 198 241 160 67 + +ac$expected +#> 1 2 3 4 5 +#> 206.77703 199.36076 192.21049 185.31667 89.33505

The saved results may also be plotted:

-plot(ac)
+plot(ac)

-

The ageChildren() function can be applied to each sex separately. To males:

+

The ageChildren() function can be applied to each sex +separately. To males:

-acM <- ageChildren(svy$age[svy$sex == 1], u5mr = 1) 
-
-acM
-#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 15.8496, df = 4, p = 0.0032
-
-plot(acM)
+acM <- ageChildren(svy$age[svy$sex == 1], u5mr = 1) + +acM +#> +#> Age Test (Children) +#> +#> X-squared = 15.8496, df = 4, p = 0.0032 + +plot(acM)

and to females:

-acF <- ageChildren(svy$age[svy$sex == 2], u5mr = 1) 
-
-acF
-#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 6.8429, df = 4, p = 0.1444
-
-plot(acF)
+acF <- ageChildren(svy$age[svy$sex == 2], u5mr = 1) + +acF +#> +#> Age Test (Children) +#> +#> X-squared = 6.8429, df = 4, p = 0.1444 + +plot(acF)

An easier way of doing this is:

-by(svy$age, svy$sex, ageChildren, u5mr = 1)
-#> svy$sex: 1
-#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 15.8496, df = 4, p = 0.0032
-#> 
-#> ------------------------------------------------------------ 
-#> svy$sex: 2
-#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 6.8429, df = 4, p = 0.1444
-

The test statistics should be interpreted with caution. A significant test result may, for example, be due to the use of an inappropriate model to generate expected numbers.

+by(svy$age, svy$sex, ageChildren, u5mr = 1) +#> svy$sex: 1 +#> +#> Age Test (Children) +#> +#> X-squared = 15.8496, df = 4, p = 0.0032 +#> +#> ------------------------------------------------------------ +#> svy$sex: 2 +#> +#> Age Test (Children) +#> +#> X-squared = 6.8429, df = 4, p = 0.1444 +

The test statistics should be interpreted with caution. A significant +test result may, for example, be due to the use of an inappropriate +model to generate expected numbers.

A significant result in this particular test may be due to:

    -
  • Specifying an inappropriate under five years mortality rate: This is a particular problem because the specified under five years mortality rate is assumed to have applied for five years prior to data being collected.

  • -
  • The assumption of a 1:1 male to female sex ratio: This is a particular problem in setting in which there is sex-selective abortion and sex-selective infanticide.

  • +
  • Specifying an inappropriate under five years mortality +rate: This is a particular problem because the specified under +five years mortality rate is assumed to have applied for five years +prior to data being collected.

  • +
  • The assumption of a 1:1 male to female sex +ratio: This is a particular problem in setting in which there +is sex-selective abortion and sex-selective infanticide.

-

The model is crude. Mortality is related to age. Younger children have a greater mortality risk than older children and only an average under five years mortality rate is used. A more sophisticated model could be used but, in many settings, we will not have the data required to use such a model.

-

It should also be noted that the sample sizes used in most survey can cause tests to yield statistically significant results for small differences between observed and expected numbers.

+

The model is crude. Mortality is related to age. Younger children +have a greater mortality risk than older children and only an average +under five years mortality rate is used. A more sophisticated model +could be used but, in many settings, we will not have the data required +to use such a model.

+

It should also be noted that the sample sizes used in most survey can +cause tests to yield statistically significant results for small +differences between observed and expected numbers.

-
-

-Use of census data

-

The use of simple demographic models is far from ideal. It is usually better to calculate the expected proportions from census data. A useful source of census data is the United States Census Bureau’s International Data Base:

-

https://www.census.gov/data-tools/demo/idb/informationGateway.php

-

The population in single year age-groups for 0, 1, 2, 3, and 4 years for Afghanistan in 2015 was:

- +
+

Use of census data +

+

The use of simple demographic models is far from ideal. It is usually +better to calculate the expected proportions from census data. A useful +source of census data is the United +States Census Bureau’s International Data Base:

+

https://www.census.gov/data-tools/demo/idb/informationGateway.php

+

The population in single year age-groups for 0, 1, 2, 3, and 4 years +for Afghanistan in 2015 was:

+
Age @@ -559,358 +631,438 @@

We can calculate expected values from these data:

-pop <- c(1148379, 1062635, 1015688, 981288, 950875) 
-ep <- pop / sum(pop)
-

With a sample size of \(n = 900\) the expected number in each age-group would be:

+pop <- c(1148379, 1062635, 1015688, 981288, 950875) +ep <- pop / sum(pop)
+

With a sample size of \(n = 900\) +the expected number in each age-group would be:

-expected <- ep * 900
-expected
-#> [1] 200.3427 185.3841 177.1939 171.1925 165.8868
-

These expected values can be used in a Chi-squared test as is illustrated above.

-

Census data may also be used to estimate the under five years’ mortality rate (U5MR) which can then be used with the ageChildren() function.

-

The model of exponential decay in a population in which births and deaths balance each other with a 1:1 male to female sex ratio:

+expected <- ep * 900 +expected +#> [1] 200.3427 185.3841 177.1939 171.1925 165.8868 +

These expected values can be used in a Chi-squared test as is +illustrated above.

+

Census data may also be used to estimate the under five years’ +mortality rate (U5MR) which can then be used with the +ageChildren() function.

+

The model of exponential decay in a population in which births and +deaths balance each other with a 1:1 male to female sex ratio:

\[ p ~ = ~ e ^ {-zt} \]

-

means that we can, given an age-distribution, estimate mortality by fitting the model:

-

\[ \log_e(n) ~ = ~ \alpha ~ + ~ \beta t \]

-

where \(n\) is the count of children in each age-group.

-

The absolute value of the β coefficient is the point estimate of the mortality rate (z). Using the 2015 population data for Afghanistan:

+

means that we can, given an age-distribution, estimate mortality by +fitting the model:

+

\[ \log_e(n) ~ = ~ \alpha ~ + ~ \beta t +\]

+

where \(n\) is the count of children +in each age-group.

+

The absolute value of the β coefficient is the point estimate of the +mortality rate (z). Using the 2015 population data for Afghanistan:

-t <- 0:4 
-lm(log(pop) ~ t)
+t <- 0:4 +lm(log(pop) ~ t)

This gives:

-
#> 
-#> Call:
-#> lm(formula = log(pop) ~ t)
-#> 
-#> Coefficients:
-#> (Intercept)            t  
-#>    13.93601     -0.04571
-

The value reported under t is the \(\beta\) coefficient (-0.04571). The absolute value of the \(\beta\) coefficient (i.e. the value without the sign) is 0.04571. This is the point estimate of the mortality rate. Expressed as the number of deaths / 10,000 persons / day:

+
#> 
+#> Call:
+#> lm(formula = log(pop) ~ t)
+#> 
+#> Coefficients:
+#> (Intercept)            t  
+#>    13.93601     -0.04571
+

The value reported under t is the \(\beta\) coefficient +(-0.04571). The absolute value of the \(\beta\) coefficient (i.e. the value without +the sign) is 0.04571. This is the point estimate of the +mortality rate. Expressed as the number of deaths / 10,000 persons / +day:

-(0.04571 / 365.25) * 10000
+(0.04571 / 365.25) * 10000

this is:

-
#> [1] 1.251472
-

We can use this estimate with the ageChildren() function:

+
#> [1] 1.251472
+

We can use this estimate with the ageChildren() +function:

-ageChildren(svy$age, u5mr = 1.251472)
-#> 
-#>  Age Test (Children)
-#> 
-#> X-squared = 20.4744, df = 4, p = 0.0004
-
-

-The age ratio

-

A much simpler and less problematic age-related test of survey and data quality is the age ratio test.

+ageChildren(svy$age, u5mr = 1.251472) +#> +#> Age Test (Children) +#> +#> X-squared = 20.4744, df = 4, p = 0.0004
+
+

The age ratio +

+

A much simpler and less problematic age-related test of survey and +data quality is the age ratio test.

The age ratio is defined as:

-

\[ \text{Age ratio} ~ = ~ \frac{\text{number of children aged between 6 and 29 months}}{\text{number of children aged between 30 and 59 months}} \]

-

We will use the recode() function from NiPN data quality toolkit to create the relevant age-groups:

+

\[ \text{Age ratio} ~ = ~ +\frac{\text{number of children aged between 6 and 29 +months}}{\text{number of children aged between 30 and 59 months}} +\]

+

We will use the recode() function from NiPN data quality +toolkit to create the relevant age-groups:

-svy$ageGroup <- recode(svy$age, "6:29=1; 30:59=2")
-head(svy)
-#>   psu age sex weight height muac oedema ycag ageLabel ageCuts ageGroup
-#> 1   1   6   1    7.3   65.0  146      2    1     6:17  (0,17]        1
-#> 2   1  42   2   12.5   89.5  156      2    4    42:53 (41,53]        2
-#> 3   1  23   1   10.6   78.1  149      2    2    18:29 (17,29]        1
-#> 4   1  18   1   12.8   81.5  160      2    2    18:29 (17,29]        1
-#> 5   1  52   1   12.1   87.3  152      2    4    42:53 (41,53]        2
-#> 6   1  36   2   16.9   93.0  190      2    3    30:41 (29,41]        2
+svy$ageGroup <- recode(svy$age, "6:29=1; 30:59=2") +head(svy) +#> psu age sex weight height muac oedema ycag ageLabel ageCuts ageGroup +#> 1 1 6 1 7.3 65.0 146 2 1 6:17 (0,17] 1 +#> 2 1 42 2 12.5 89.5 156 2 4 42:53 (41,53] 2 +#> 3 1 23 1 10.6 78.1 149 2 2 18:29 (17,29] 1 +#> 4 1 18 1 12.8 81.5 160 2 2 18:29 (17,29] 1 +#> 5 1 52 1 12.1 87.3 152 2 4 42:53 (41,53] 2 +#> 6 1 36 2 16.9 93.0 190 2 3 30:41 (29,41] 2

The observed age ratio is:

-sum(svy$ageGroup == 1) / sum(svy$ageGroup == 2)
+sum(svy$ageGroup == 1) / sum(svy$ageGroup == 2)

which gives:

-
#> [1] 0.8653846
-

It is often easier to work with proportions than with ratios so we only need to calculate the proportion in the younger age-group:

+
#> [1] 0.8653846
+

It is often easier to work with proportions than with ratios so we +only need to calculate the proportion in the younger age-group:

-sum(svy$ageGroup == 1) / sum(table(svy$ageGroup))
+sum(svy$ageGroup == 1) / sum(table(svy$ageGroup))

which gives:

-
#> [1] 0.4639175
-

We can calculate an expected value using census data or a simple demographic model. The simplest approach is to use a standard value. SMART surveys often use the ratio 0.85:1.

-

We only need to calculate the expected proportion in the younger group. For the ratio 0.85:1 this is:

+
#> [1] 0.4639175
+

We can calculate an expected value using census data or a simple +demographic model. The simplest approach is to use a standard value. +SMART surveys often use the ratio 0.85:1.

+

We only need to calculate the expected proportion in the younger +group. For the ratio 0.85:1 this is:

-p <- 0.85 / (0.85 + 1) 
+p <- 0.85 / (0.85 + 1)

This gives:

-
#> [1] 0.4594595
-

The observed proportion (0.4639175) and expected proportion (0.4594595) are so similar that a formal test of statistical significance is not required in this case.

+
#> [1] 0.4594595
+

The observed proportion (0.4639175) and expected proportion +(0.4594595) are so similar that a formal test of statistical +significance is not required in this case.

Formal testing can be done using a Chi-squared test:

-prop.test(sum(svy$ageGroup == 1), sum(table(svy$ageGroup)), p = 0.4594595)
+prop.test(sum(svy$ageGroup == 1), sum(table(svy$ageGroup)), p = 0.4594595)

This returns:

-
#> 
-#>  1-sample proportions test with continuity correction
-#> 
-#> data:  sum(svy$ageGroup == 1) out of sum(table(svy$ageGroup)), null probability 0.4594595
-#> X-squared = 0.053062, df = 1, p-value = 0.8178
-#> alternative hypothesis: true p is not equal to 0.4594595
-#> 95 percent confidence interval:
-#>  0.4304994 0.4976573
-#> sample estimates:
-#>         p 
-#> 0.4639175
-

The age ratio in the example data is not significantly different from the expected age ratio.

-

The NiPN data quality toolkit provide an R function called ageRatioTest() that performs the age ratio test:

+
#> 
+#>  1-sample proportions test with continuity correction
+#> 
+#> data:  sum(svy$ageGroup == 1) out of sum(table(svy$ageGroup)), null probability 0.4594595
+#> X-squared = 0.053062, df = 1, p-value = 0.8178
+#> alternative hypothesis: true p is not equal to 0.4594595
+#> 95 percent confidence interval:
+#>  0.4304994 0.4976573
+#> sample estimates:
+#>         p 
+#> 0.4639175
+

The age ratio in the example data is not significantly different from +the expected age ratio.

+

The NiPN data quality toolkit provide an R function called +ageRatioTest() that performs the age ratio test:

-ageRatioTest(svy$age, ratio = 0.85)
+ageRatioTest(svy$age, ratio = 0.85)

This returns:

-
#> 
-#>      Age Ratio Test (children's data)
-#> 
-#>                     Expected age ratio = 0.8500
-#> Expected proportion aged 6 - 29 months = 0.4595
-#> 
-#>                     Observed age ratio = 0.8654
-#> Observed proportion aged 6 - 29 months = 0.4639
-#> 
-#> X-squared = 0.0531, p = 0.8178
-

The ratio parameter of the ageRatioTest() function allows you to specify an expected age ratio other than 0.85:1.

-

Note that the ageRatioTest() function applies the test to data from children aged between 6 and 59 months only (all other ages are ignored).

-

The age ratio test might be applied to data from both sexes (as above) and to each sex separately:

+
#> 
+#>      Age Ratio Test (children's data)
+#> 
+#>                     Expected age ratio = 0.8500
+#> Expected proportion aged 6 - 29 months = 0.4595
+#> 
+#>                     Observed age ratio = 0.8654
+#> Observed proportion aged 6 - 29 months = 0.4639
+#> 
+#> X-squared = 0.0531, p = 0.8178
+

The ratio parameter of the ageRatioTest() +function allows you to specify an expected age ratio other than +0.85:1.

+

Note that the ageRatioTest() function applies the test +to data from children aged between 6 and 59 months only (all other ages +are ignored).

+

The age ratio test might be applied to data from both sexes (as +above) and to each sex separately:

-by(svy$age, svy$sex, ageRatioTest, ratio = 0.85)
-#> svy$sex: 1
-#> 
-#>      Age Ratio Test (children's data)
-#> 
-#>                     Expected age ratio = 0.8500
-#> Expected proportion aged 6 - 29 months = 0.4595
-#> 
-#>                     Observed age ratio = 0.8638
-#> Observed proportion aged 6 - 29 months = 0.4635
-#> 
-#> X-squared = 0.0145, p = 0.9041
-#> 
-#> ------------------------------------------------------------ 
-#> svy$sex: 2
-#> 
-#>      Age Ratio Test (children's data)
-#> 
-#>                     Expected age ratio = 0.8500
-#> Expected proportion aged 6 - 29 months = 0.4595
-#> 
-#>                     Observed age ratio = 0.8670
-#> Observed proportion aged 6 - 29 months = 0.4644
-#> 
-#> X-squared = 0.0247, p = 0.8750
-

The example data meets expectations regarding the age ratio for all children and for male and female children separately.

+by(svy$age, svy$sex, ageRatioTest, ratio = 0.85) +#> svy$sex: 1 +#> +#> Age Ratio Test (children's data) +#> +#> Expected age ratio = 0.8500 +#> Expected proportion aged 6 - 29 months = 0.4595 +#> +#> Observed age ratio = 0.8638 +#> Observed proportion aged 6 - 29 months = 0.4635 +#> +#> X-squared = 0.0145, p = 0.9041 +#> +#> ------------------------------------------------------------ +#> svy$sex: 2 +#> +#> Age Ratio Test (children's data) +#> +#> Expected age ratio = 0.8500 +#> Expected proportion aged 6 - 29 months = 0.4595 +#> +#> Observed age ratio = 0.8670 +#> Observed proportion aged 6 - 29 months = 0.4644 +#> +#> X-squared = 0.0247, p = 0.8750 +

The example data meets expectations regarding the age ratio for all +children and for male and female children separately.

-
-

-Age and sex distributions : Adults and general population surveys

-

A key test of survey quality is whether the survey data represents the population in terms of the age and sex distribution. We can test this by comparison with census data.

+
+

Age and sex distributions : Adults and general population +surveys +

+

A key test of survey quality is whether the survey data represents +the population in terms of the age and sex distribution. We can test +this by comparison with census data.

We will retrieve some example data:

-svy <- read.table("as.ex01.csv", header = TRUE, sep = ",") 
-head(svy)
-
#>   age sex
-#> 1  44   2
-#> 2   1   2
-#> 3  15   2
-#> 4   7   1
-#> 5  14   1
-#> 6  14   1
-

These data are taken from household rosters collected as part of a household survey in Tanzania. We will use census data taken from the Wolfram|Alpha knowledge engine:

-

http://www.wolframalpha.com/input/?i=Tanzania+age+distribution

-

Another useful source of census data is the United States Census Bureau’s International Data Base:

-

https://www.census.gov/data-tools/demo/idb/informationGateway.php

-

The pyramid plot produced by Wolfram|Alpha is shown in the figure below.

-

-

The table produced by Wolfram|Alpha was downloaded and stored in a CSV file:

+svy <- read.table("as.ex01.csv", header = TRUE, sep = ",") +head(svy)
+
#>   age sex
+#> 1  44   2
+#> 2   1   2
+#> 3  15   2
+#> 4   7   1
+#> 5  14   1
+#> 6  14   1
+

These data are taken from household rosters collected as part of a +household survey in Tanzania. We will use census data taken from the +Wolfram|Alpha knowledge engine:

+

http://www.wolframalpha.com/input/?i=Tanzania+age+distribution

+

Another useful source of census data is the United States Census +Bureau’s International Data Base:

+

https://www.census.gov/data-tools/demo/idb/informationGateway.php

+

The pyramid plot produced by Wolfram|Alpha is shown in the figure +below.

+

+

The table produced by Wolfram|Alpha was downloaded and stored in a +CSV file:

-ref <- read.table("as.ex02.csv", header = TRUE, sep = ",")
-ref
-
#>         age   Males Females     All
-#> 1     [0,5) 4043000 3969000 8012000
-#> 2    [5,10) 3336000 3284000 6620000
-#> 3   [10,15) 2775000 2742000 5517000
-#> 4   [15,20) 2386000 2372000 4758000
-#> 5   [20,25) 2076000 2073000 4149000
-#> 6   [25,30) 1753000 1750000 3503000
-#> 7   [30,35) 1453000 1432000 2885000
-#> 8   [35,40) 1142000 1099000 2241000
-#> 9   [40,45)  873000  846000 1719000
-#> 10  [45,50)  673000  699000 1372000
-#> 11  [50,55)  538000  601000 1139000
-#> 12  [55,60)  433000  503000  936000
-#> 13  [60,65)  357000  426000  783000
-#> 14  [65,70)  266000  319000  585000
-#> 15  [70,75)  182000  222000  404000
-#> 16  [75,80)  108000  137000  245000
-#> 17  [80,85)   51000   68000  119000
-#> 18  [85,90)   17000   25000   42000
-#> 19  [90,95)    3000    6000    9000
-#> 20 [95,100)       0    1000    1000
-

The age-groups are expressed using the form specified in ISO 31-11, an international standard that applies to mathematical symbols. The form [a,b) expresses the interval \(a ≤ x < b\). For example, [30,35) is used to indicate the set {30, 31, 32, 33, 34} of ages in years. The form [a,b) is said to be closed on the left and open on the right.

-

The reference data (ref) uses five-year age-groups. We will create the same age-groups in the example dataset.

+ref <- read.table("as.ex02.csv", header = TRUE, sep = ",") +ref
+
#>         age   Males Females     All
+#> 1     [0,5) 4043000 3969000 8012000
+#> 2    [5,10) 3336000 3284000 6620000
+#> 3   [10,15) 2775000 2742000 5517000
+#> 4   [15,20) 2386000 2372000 4758000
+#> 5   [20,25) 2076000 2073000 4149000
+#> 6   [25,30) 1753000 1750000 3503000
+#> 7   [30,35) 1453000 1432000 2885000
+#> 8   [35,40) 1142000 1099000 2241000
+#> 9   [40,45)  873000  846000 1719000
+#> 10  [45,50)  673000  699000 1372000
+#> 11  [50,55)  538000  601000 1139000
+#> 12  [55,60)  433000  503000  936000
+#> 13  [60,65)  357000  426000  783000
+#> 14  [65,70)  266000  319000  585000
+#> 15  [70,75)  182000  222000  404000
+#> 16  [75,80)  108000  137000  245000
+#> 17  [80,85)   51000   68000  119000
+#> 18  [85,90)   17000   25000   42000
+#> 19  [90,95)    3000    6000    9000
+#> 20 [95,100)       0    1000    1000
+

The age-groups are expressed using the form specified in ISO 31-11, +an international standard that applies to mathematical symbols. The form +[a,b) expresses the interval \(a ≤ x < +b\). For example, [30,35) is used to indicate the set {30, 31, +32, 33, 34} of ages in years. The form [a,b) is said to be +closed on the left and open on the right.

+

The reference data (ref) uses five-year age-groups. We +will create the same age-groups in the example dataset.

We should first check the range of ages in the example data:

-range(svy$age)
+range(svy$age)

which returns:

-
#> [1]  0 93
-

The R language provides a function that makes it easy to create ISO 31-11 groupings from raw data:

+
#> [1]  0 93
+

The R language provides a function that makes it easy to create ISO +31-11 groupings from raw data:

-svy$ageGroup <-cut(svy$age, 
-                   breaks = seq(from = 0, to = 95, by = 5),
-                   include.lowest = TRUE, right = FALSE)
-

Using include.lowest = TRUE tells the cut() function to include the lowest breaks value (zero in this case). Using right = FALSE tells the cut() function to use groupings that are closed on the left. This combination of parameters creates the same “closed on the left” and “open on the right” age-groups as are used in the reference (ref) data:

+svy$ageGroup <-cut(svy$age, + breaks = seq(from = 0, to = 95, by = 5), + include.lowest = TRUE, right = FALSE) +

Using include.lowest = TRUE tells the cut() +function to include the lowest breaks value (zero in this case). Using +right = FALSE tells the cut() function to use +groupings that are closed on the left. This combination of parameters +creates the same “closed on the left” and “open on the right” age-groups +as are used in the reference (ref) data:

-table(svy$ageGroup)
-#> 
-#>   [0,5)  [5,10) [10,15) [15,20) [20,25) [25,30) [30,35) [35,40) [40,45) [45,50) 
-#>    1598    1268    1072     808     870     575     580     385     424     258 
-#> [50,55) [55,60) [60,65) [65,70) [70,75) [75,80) [80,85) [85,90) [90,95] 
-#>     284     128     165      82      98      51      60      18      12
+table(svy$ageGroup) +#> +#> [0,5) [5,10) [10,15) [15,20) [20,25) [25,30) [30,35) [35,40) [40,45) [45,50) +#> 1598 1268 1072 808 870 575 580 385 424 258 +#> [50,55) [55,60) [60,65) [65,70) [70,75) [75,80) [80,85) [85,90) [90,95] +#> 284 128 165 82 98 51 60 18 12

A tabular analysis of age-group by sex can be produced using:

-table(svy$ageGroup, svy$sex)
-#>          
-#>             1   2
-#>   [0,5)   821 777
-#>   [5,10)  637 631
-#>   [10,15) 547 525
-#>   [15,20) 389 419
-#>   [20,25) 342 528
-#>   [25,30) 343 232
-#>   [30,35) 250 330
-#>   [35,40) 177 208
-#>   [40,45) 206 218
-#>   [45,50) 125 133
-#>   [50,55) 162 122
-#>   [55,60)  70  58
-#>   [60,65)  87  78
-#>   [65,70)  33  49
-#>   [70,75)  47  51
-#>   [75,80)  22  29
-#>   [80,85)  24  36
-#>   [85,90)  10   8
-#>   [90,95]   1  11
+table(svy$ageGroup, svy$sex) +#> +#> 1 2 +#> [0,5) 821 777 +#> [5,10) 637 631 +#> [10,15) 547 525 +#> [15,20) 389 419 +#> [20,25) 342 528 +#> [25,30) 343 232 +#> [30,35) 250 330 +#> [35,40) 177 208 +#> [40,45) 206 218 +#> [45,50) 125 133 +#> [50,55) 162 122 +#> [55,60) 70 58 +#> [60,65) 87 78 +#> [65,70) 33 49 +#> [70,75) 47 51 +#> [75,80) 22 29 +#> [80,85) 24 36 +#> [85,90) 10 8 +#> [90,95] 1 11

A visual inspection is useful:

-pyramid.plot(svy$ageGroup, svy$sex)
+pyramid.plot(svy$ageGroup, svy$sex)

We can make this easier to read:

-pyramid.plot(svy$ageGroup, 
-             svy$sex, 
-             main = "Age-group by sex",
-             xlab = "Number (Males | Females)", 
-             ylab = "", 
-             las = 1, 
-             cex.names = 0.9)
+pyramid.plot(svy$ageGroup, + svy$sex, + main = "Age-group by sex", + xlab = "Number (Males | Females)", + ylab = "", + las = 1, + cex.names = 0.9)

-

Note that we specified ylab = "" because it is clear that the category labels represent age-groups and to prevent the y-axis label from obscuring the category labels, which happens with:

+

Note that we specified ylab = "" because it is clear +that the category labels represent age-groups and to prevent the y-axis +label from obscuring the category labels, which happens with:

-pyramid.plot(svy$ageGroup, 
-             svy$sex, 
-             main = "Age-group by sex",
-             xlab = "Number (Males | Females)", 
-             ylab = "Age-group", 
-             las = 1,
-             cex.names = 0.9)
+pyramid.plot(svy$ageGroup, + svy$sex, + main = "Age-group by sex", + xlab = "Number (Males | Females)", + ylab = "Age-group", + las = 1, + cex.names = 0.9)

-

It is possible to alter the number of lines of text in margins of the plot, reduce the size of the age-group labels, and place the y-axis label on a specific line in the left margin of the plot in order to make a clearer plot:

+

It is possible to alter the number of lines of text in margins of the +plot, reduce the size of the age-group labels, and place the y-axis +label on a specific line in the left margin of the plot in order to make +a clearer plot:

-par(mar = c(5, 5, 4, 2))
-
-pyramid.plot(svy$ageGroup, 
-             svy$sex, 
-             main = "Age-group by sex",
-             xlab = "Number (Males | Females)", 
-             ylab = "", 
-             las = 1, 
-             cex.names = 0.8)
-
-title(ylab = "Age-group", line = 4)
+par(mar = c(5, 5, 4, 2)) + +pyramid.plot(svy$ageGroup, + svy$sex, + main = "Age-group by sex", + xlab = "Number (Males | Females)", + ylab = "", + las = 1, + cex.names = 0.8) + +title(ylab = "Age-group", line = 4)

-

The easiest way of checking whether the survey data represents the general population in terms of the age and sex distribution is to compare the observed (figure on right) and expected (figure on left) distributions.

-

-

The general shapes of the two distributions are similar. Some of the lumpiness in figure on the right is due to age heaping in the adult ages at decades and half-decades:

+

The easiest way of checking whether the survey data represents the +general population in terms of the age and sex distribution is to +compare the observed (figure on right) and expected (figure on left) +distributions.

+

+

The general shapes of the two distributions are similar. Some of the +lumpiness in figure on the right is due to age heaping in the adult ages +at decades and half-decades:

-ah <- ageHeaping(svy$age, divisor = 10) 
-plot(ah, main = "Remainder of age / 10")
+ah <- ageHeaping(svy$age, divisor = 10) +plot(ah, main = "Remainder of age / 10")

-

A more formal test of the age structure can be made by comparing observed and expected numbers. We can do this graphically:

+

A more formal test of the age structure can be made by comparing +observed and expected numbers. We can do this graphically:

-ref <- ref[1:19, ]
-
-expectedProportions <- ref$All / sum(ref$All)
-expectedNumbers <- expectedProportions * sum(table(svy$ageGroup))
-
-mp <- barplot(table(svy$ageGroup), 
-              main = "Observed and expected numbers", 
-              ylim = c(0, max(expectedNumbers)), 
-              las = 2)
-
-lines(mp, expectedNumbers, lty = 2, lwd = 2)
+ref <- ref[1:19, ] + +expectedProportions <- ref$All / sum(ref$All) +expectedNumbers <- expectedProportions * sum(table(svy$ageGroup)) + +mp <- barplot(table(svy$ageGroup), + main = "Observed and expected numbers", + ylim = c(0, max(expectedNumbers)), + las = 2) + +lines(mp, expectedNumbers, lty = 2, lwd = 2)

-

The observed and expected numbers are similar to each other. The lumpiness in the observed numbers is due to age heaping. See Figure ASA04.

+

The observed and expected numbers are similar to each other. The +lumpiness in the observed numbers is due to age heaping. See Figure +ASA04.

Formal testing can be performed:

-chisq.test(table(svy$ageGroup), 
-           p = expectedProportions)
+chisq.test(table(svy$ageGroup), + p = expectedProportions)

This gives:

-
#> Warning in chisq.test(table(svy$ageGroup), p = expectedProportions): Chi-squared
-#> approximation may be incorrect
-#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  table(svy$ageGroup)
-#> X-squared = 248.41, df = 18, p-value < 2.2e-16
-

The warning is due to small expected numbers (i.e. n < 5) in the older age-groups. R provides a more robust “Monte Carlo” test:

+
#> Warning in chisq.test(table(svy$ageGroup), p = expectedProportions): Chi-squared
+#> approximation may be incorrect
+#> 
+#>  Chi-squared test for given probabilities
+#> 
+#> data:  table(svy$ageGroup)
+#> X-squared = 248.41, df = 18, p-value < 2.2e-16
+

The warning is due to small expected numbers (i.e. n < 5) in the +older age-groups. R provides a more robust “Monte Carlo” test:

-chisq.test(table(svy$ageGroup), 
-           p = expectedProportions, 
-           simulate.p.value = TRUE)
+chisq.test(table(svy$ageGroup), + p = expectedProportions, + simulate.p.value = TRUE)

This may take a few seconds to compute and yields:

-
#> 
-#>  Chi-squared test for given probabilities with simulated p-value (based
-#>  on 2000 replicates)
-#> 
-#> data:  table(svy$ageGroup)
-#> X-squared = 248.41, df = NA, p-value = 0.0004998
-

The test results need to be interpreted with caution. The sample size (\(n = 8736\)) is large in this example. This means that small differences, which may be due to age heaping, become statistically significant. This test cannot be considered to be good evidence that the age-structure of the sample differs from the expected age-structure of the population. We also need to examine the sex ratio of the sample. A sex ratio test can be performed using the sexRatioTest() function from the NiPN data quality toolkit and the sex ratio observed in the census data:

+
#> 
+#>  Chi-squared test for given probabilities with simulated p-value (based
+#>  on 2000 replicates)
+#> 
+#> data:  table(svy$ageGroup)
+#> X-squared = 248.41, df = NA, p-value = 0.0004998
+

The test results need to be interpreted with caution. The sample size +(\(n = 8736\)) is large in this +example. This means that small differences, which may be due to age +heaping, become statistically significant. This test cannot be +considered to be good evidence that the age-structure of the sample +differs from the expected age-structure of the population. We also need +to examine the sex ratio of the sample. A sex ratio test can be +performed using the sexRatioTest() function from the NiPN +data quality toolkit and the sex ratio observed in the census data:

-censusM <- sum(ref$Males)
-censusF <- sum(ref$Females)
-
-sexRatioTest(svy$sex, 
-             codes = c(1, 2), 
-             pop = c(censusM, censusF))
+censusM <- sum(ref$Males) +censusF <- sum(ref$Females) + +sexRatioTest(svy$sex, + codes = c(1, 2), + pop = c(censusM, censusF))

This yields:

-
#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.4988
-#> Observed proportion male = 0.4914
-#> X-squared = 1.8770, p = 0.1707
-

There is no evidence that the sex ratio in the sample differs much from the expected sex ratio in the population.

-

The techniques outlined in this section are illustrative. This is because many surveys, other than nutritional anthropometry surveys in young children, are not standardised. A survey may sample only women of child-bearing age. The sample may be restricted to women aged between 15 and 45 years.

-

In this case the age-structure can be examined using the techniques outlined above but it would make no sense to examine the sex ratio. Care should be taken when examining data from surveys that may have deliberately oversampled specific age-groups.

+
#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.4988
+#> Observed proportion male = 0.4914
+#> X-squared = 1.8770, p = 0.1707
+

There is no evidence that the sex ratio in the sample differs much +from the expected sex ratio in the population.

+

The techniques outlined in this section are illustrative. This is +because many surveys, other than nutritional anthropometry surveys in +young children, are not standardised. A survey may sample only women of +child-bearing age. The sample may be restricted to women aged between 15 +and 45 years.

+

In this case the age-structure can be examined using the techniques +outlined above but it would make no sense to examine the sex ratio. Care +should be taken when examining data from surveys that may have +deliberately oversampled specific age-groups.

- - - - + -
+ diff --git a/docs/articles/as_files/figure-html/as10d-1.png b/docs/articles/as_files/figure-html/as10d-1.png index 7c87957..0c8eac0 100644 Binary files a/docs/articles/as_files/figure-html/as10d-1.png and b/docs/articles/as_files/figure-html/as10d-1.png differ diff --git a/docs/articles/as_files/figure-html/as11-1.png b/docs/articles/as_files/figure-html/as11-1.png index 7f5576b..f4b91f1 100644 Binary files a/docs/articles/as_files/figure-html/as11-1.png and b/docs/articles/as_files/figure-html/as11-1.png differ diff --git a/docs/articles/as_files/figure-html/as12-1.png b/docs/articles/as_files/figure-html/as12-1.png index 6d55efc..a4015c6 100644 Binary files a/docs/articles/as_files/figure-html/as12-1.png and b/docs/articles/as_files/figure-html/as12-1.png differ diff --git a/docs/articles/as_files/figure-html/as35-1.png b/docs/articles/as_files/figure-html/as35-1.png index c69c9fa..0ae1fd1 100644 Binary files a/docs/articles/as_files/figure-html/as35-1.png and b/docs/articles/as_files/figure-html/as35-1.png differ diff --git a/docs/articles/as_files/figure-html/as35a-1.png b/docs/articles/as_files/figure-html/as35a-1.png index 36f09cc..06542cc 100644 Binary files a/docs/articles/as_files/figure-html/as35a-1.png and b/docs/articles/as_files/figure-html/as35a-1.png differ diff --git a/docs/articles/as_files/figure-html/as35b-1.png b/docs/articles/as_files/figure-html/as35b-1.png index bd8490f..c2fbbe8 100644 Binary files a/docs/articles/as_files/figure-html/as35b-1.png and b/docs/articles/as_files/figure-html/as35b-1.png differ diff --git a/docs/articles/as_files/figure-html/as35c-1.png b/docs/articles/as_files/figure-html/as35c-1.png index f56d9e9..80a906b 100644 Binary files a/docs/articles/as_files/figure-html/as35c-1.png and b/docs/articles/as_files/figure-html/as35c-1.png differ diff --git a/docs/articles/as_files/figure-html/as35d-2.png b/docs/articles/as_files/figure-html/as35d-2.png index f56d9e9..80a906b 100644 Binary files a/docs/articles/as_files/figure-html/as35d-2.png and b/docs/articles/as_files/figure-html/as35d-2.png differ diff --git a/docs/articles/as_files/figure-html/as36-1.png b/docs/articles/as_files/figure-html/as36-1.png index 4cb0fef..866764e 100644 Binary files a/docs/articles/as_files/figure-html/as36-1.png and b/docs/articles/as_files/figure-html/as36-1.png differ diff --git a/docs/articles/as_files/figure-html/as37-1.png b/docs/articles/as_files/figure-html/as37-1.png index 68210fe..4ba0a8d 100644 Binary files a/docs/articles/as_files/figure-html/as37-1.png and b/docs/articles/as_files/figure-html/as37-1.png differ diff --git a/docs/articles/as_files/figure-html/as6-1.png b/docs/articles/as_files/figure-html/as6-1.png index 46034a7..a9369ed 100644 Binary files a/docs/articles/as_files/figure-html/as6-1.png and b/docs/articles/as_files/figure-html/as6-1.png differ diff --git a/docs/articles/as_files/figure-html/as6a-1.png b/docs/articles/as_files/figure-html/as6a-1.png index 2ac5f1f..8e0a502 100644 Binary files a/docs/articles/as_files/figure-html/as6a-1.png and b/docs/articles/as_files/figure-html/as6a-1.png differ diff --git a/docs/articles/as_files/figure-html/as6b-1.png b/docs/articles/as_files/figure-html/as6b-1.png index 2dae431..4fe8d8d 100644 Binary files a/docs/articles/as_files/figure-html/as6b-1.png and b/docs/articles/as_files/figure-html/as6b-1.png differ diff --git a/docs/articles/as_files/figure-html/as6c-1.png b/docs/articles/as_files/figure-html/as6c-1.png index 66c1117..773a088 100644 Binary files a/docs/articles/as_files/figure-html/as6c-1.png and b/docs/articles/as_files/figure-html/as6c-1.png differ diff --git a/docs/articles/as_files/figure-html/as6d-1.png b/docs/articles/as_files/figure-html/as6d-1.png index 2ac5f1f..8e0a502 100644 Binary files a/docs/articles/as_files/figure-html/as6d-1.png and b/docs/articles/as_files/figure-html/as6d-1.png differ diff --git a/docs/articles/as_files/figure-html/as7-1.png b/docs/articles/as_files/figure-html/as7-1.png index 05b7027..cec6fd4 100644 Binary files a/docs/articles/as_files/figure-html/as7-1.png and b/docs/articles/as_files/figure-html/as7-1.png differ diff --git a/docs/articles/as_files/figure-html/as7a-1.png b/docs/articles/as_files/figure-html/as7a-1.png index 4606a2a..991d7f6 100644 Binary files a/docs/articles/as_files/figure-html/as7a-1.png and b/docs/articles/as_files/figure-html/as7a-1.png differ diff --git a/docs/articles/as_files/figure-html/as7b-1.png b/docs/articles/as_files/figure-html/as7b-1.png index 744f828..3ad74f3 100644 Binary files a/docs/articles/as_files/figure-html/as7b-1.png and b/docs/articles/as_files/figure-html/as7b-1.png differ diff --git a/docs/articles/as_files/figure-html/as9i-1.png b/docs/articles/as_files/figure-html/as9i-1.png index 28e57bd..10675c3 100644 Binary files a/docs/articles/as_files/figure-html/as9i-1.png and b/docs/articles/as_files/figure-html/as9i-1.png differ diff --git a/docs/articles/dp.html b/docs/articles/dp.html index 9720257..cf70233 100644 --- a/docs/articles/dp.html +++ b/docs/articles/dp.html @@ -4,7 +4,8 @@ - + + Digit preference • nipnTK @@ -12,143 +13,110 @@ - - - + + + - - + + - -
-
- +
-
-

This returns:

-
#> finalDigits
-#>     0     1     2     3     4     5     6     7     8     9 
-#> 0.095 0.080 0.096 0.102 0.106 0.098 0.109 0.095 0.109 0.110
+
#> finalDigits
+#>     0     1     2     3     4     5     6     7     8     9 
+#> 0.095 0.080 0.096 0.102 0.106 0.098 0.109 0.095 0.109 0.110

If you prefer working with percentages then:

-prop.table(table(finalDigits)) * 100
+prop.table(table(finalDigits)) * 100

returns:

-
#> finalDigits
-#>    0    1    2    3    4    5    6    7    8    9 
-#>  9.5  8.0  9.6 10.2 10.6  9.8 10.9  9.5 10.9 11.0
+
#> finalDigits
+#>    0    1    2    3    4    5    6    7    8    9 
+#>  9.5  8.0  9.6 10.2 10.6  9.8 10.9  9.5 10.9 11.0

Examining data graphically is very useful:

-barplot(table(finalDigits), xlab = "Final digit", ylab = "Frequency")
-

We can add a line showing our expectation that each final digit should occur about 10% of the time:

+barplot(table(finalDigits), xlab = "Final digit", ylab = "Frequency")
+

We can add a line showing our expectation that each final digit +should occur about 10% of the time:

-abline(h = sum(table(finalDigits)) / 10, lty = 3)
+abline(h = sum(table(finalDigits)) / 10, lty = 3)

The resulting plot is shown below.

-

The tabular and graphical analyses are consistent with there being little or no digit preference in the generated data.

-

Both analyses agree with the expectation that each final digit should occur about 10% of the time.

+

The tabular and graphical analyses are consistent with there being +little or no digit preference in the generated data.

+

Both analyses agree with the expectation that each final digit should +occur about 10% of the time.

All we are seeing is random variation.

We can use a formal test to confirm this:

-chisq.test(table(finalDigits))
+chisq.test(table(finalDigits))

This returns:

-
#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  table(finalDigits)
-#> X-squared = 7.72, df = 9, p-value = 0.5626
-

In this example the p-value is not below 0.05 so we accept the null hypothesis that there is no digit preference.

-

It is important to check that each digit between zero and nine is represented in tables and plots.

+
#> 
+#>  Chi-squared test for given probabilities
+#> 
+#> data:  table(finalDigits)
+#> X-squared = 7.72, df = 9, p-value = 0.5626
+

In this example the p-value is not below 0.05 so we accept +the null hypothesis that there is no digit preference.

+

It is important to check that each digit between zero and nine is +represented in tables and plots.

Missing digits can indicate strong digit preference.

-

The NiPN data quality toolkit provides the fullTable() function. This R language function produces a table that includes cells with zero counts.

-

As an example we will remove all the values with a final digit equal to 6 from our generated data:

+

The NiPN data quality toolkit provides the fullTable() +function. This R language function produces a table that includes cells +with zero counts.

+

As an example we will remove all the values with a final digit equal +to 6 from our generated data:

-finalDigits[finalDigits == 6] <- NA
+finalDigits[finalDigits == 6] <- NA

and see the effect:

-table(finalDigits)
-#> finalDigits
-#>   0   1   2   3   4   5   7   8   9 
-#>  95  80  96 102 106  98  95 109 110
-prop.table(table(finalDigits)) * 100
-#> finalDigits
-#>         0         1         2         3         4         5         7         8 
-#> 10.662177  8.978676 10.774411 11.447811 11.896745 10.998878 10.662177 12.233446 
-#>         9 
-#> 12.345679
-barplot(table(finalDigits), xlab = "Final digit", ylab = "Frequency") 
-abline(h = sum(table(finalDigits)) / 10, lty = 3) 
+table(finalDigits) +#> finalDigits +#> 0 1 2 3 4 5 7 8 9 +#> 95 80 96 102 106 98 95 109 110 +prop.table(table(finalDigits)) * 100 +#> finalDigits +#> 0 1 2 3 4 5 7 8 +#> 10.662177 8.978676 10.774411 11.447811 11.896745 10.998878 10.662177 12.233446 +#> 9 +#> 12.345679 +barplot(table(finalDigits), xlab = "Final digit", ylab = "Frequency") +abline(h = sum(table(finalDigits)) / 10, lty = 3)

-chisq.test(table(finalDigits))
-#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  table(finalDigits)
-#> X-squared = 6.8889, df = 8, p-value = 0.5487
-

This is a misleading analysis. It is very easy to miss that there are no final digits equal to 6 in the data. The plot is misleading because the final digit 6 is not represented and we assumed that there were ten rather than nine final digits when we calculated the expected frequencies. The Chi-squared test is not correct because it does not account for there being zero cases in which the final digit is equal to 6.

+chisq.test(table(finalDigits)) +#> +#> Chi-squared test for given probabilities +#> +#> data: table(finalDigits) +#> X-squared = 6.8889, df = 8, p-value = 0.5487 +

This is a misleading analysis. It is very easy to miss that there are +no final digits equal to 6 in the data. The plot is +misleading because the final digit 6 is not represented +and we assumed that there were ten rather than nine final digits when we +calculated the expected frequencies. The Chi-squared test is not correct +because it does not account for there being zero cases in which the +final digit is equal to 6.

The fullTable() function avoids these issues:

-fullTable(finalDigits)
-#>   0   1   2   3   4   5   6   7   8   9 
-#>  95  80  96 102 106  98   0  95 109 110
-prop.table(fullTable(finalDigits)) * 100
-#>         0         1         2         3         4         5         6         7 
-#> 10.662177  8.978676 10.774411 11.447811 11.896745 10.998878  0.000000 10.662177 
-#>         8         9 
-#> 12.233446 12.345679
-barplot(fullTable(finalDigits), xlab = "Final digit", ylab = "Frequency") 
-abline(h = sum(fullTable(finalDigits)) / 10, lty = 3)
+fullTable(finalDigits) +#> 0 1 2 3 4 5 6 7 8 9 +#> 95 80 96 102 106 98 0 95 109 110 +prop.table(fullTable(finalDigits)) * 100 +#> 0 1 2 3 4 5 6 7 +#> 10.662177 8.978676 10.774411 11.447811 11.896745 10.998878 0.000000 10.662177 +#> 8 9 +#> 12.233446 12.345679 +barplot(fullTable(finalDigits), xlab = "Final digit", ylab = "Frequency") +abline(h = sum(fullTable(finalDigits)) / 10, lty = 3)

-chisq.test(fullTable(finalDigits))
-#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  fullTable(finalDigits)
-#> X-squared = 106.65, df = 9, p-value < 2.2e-16
-

The Chi-squared test (incorrectly) calculated without the zero cell:

-
#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  table(finalDigits)
-#> X-squared = 6.8889, df = 8, p-value = 0.5487
+chisq.test(fullTable(finalDigits)) +#> +#> Chi-squared test for given probabilities +#> +#> data: fullTable(finalDigits) +#> X-squared = 106.65, df = 9, p-value < 2.2e-16 +

The Chi-squared test (incorrectly) calculated without the zero +cell:

+
#> 
+#>  Chi-squared test for given probabilities
+#> 
+#> data:  table(finalDigits)
+#> X-squared = 6.8889, df = 8, p-value = 0.5487

indicates that there is no problem with the data.

The chi-square test (correctly) calculated with the zero cell:

-
#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  fullTable(finalDigits)
-#> X-squared = 106.65, df = 9, p-value < 2.2e-16
+
#> 
+#>  Chi-squared test for given probabilities
+#> 
+#> data:  fullTable(finalDigits)
+#> X-squared = 106.65, df = 9, p-value < 2.2e-16

indicates that there is a problem with the data.

-

Note that we use sum(fullTable(finalDigits)) / 10 (i.e. we divide by ten) because we know that there should be ten final digits (i.e. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9).

-

There is an issue with using hypothesis test such as the chi-squared test. Test values are strongly influenced by sample size yielding false-negative results when used with small sample sizes and false-positive results when used with large sample sizes.

-

We can illustrate this by generating some new artificial data with marked digit preference:

+

Note that we use sum(fullTable(finalDigits)) / 10 +(i.e. we divide by ten) because we know that there should be ten final +digits (i.e. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9).

+

There is an issue with using hypothesis test such as the chi-squared +test. Test values are strongly influenced by sample size yielding +false-negative results when used with small sample sizes and +false-positive results when used with large sample sizes.

+

We can illustrate this by generating some new artificial data with +marked digit preference:

-finalDigits <- as.table(x = c(11, 7, 5, 4, 7, 11, 5, 4, 4, 2)) 
-names(finalDigits) <- 0:9
-

This creates a table object containing counts of imaginary final digits.

+finalDigits <- as.table(x = c(11, 7, 5, 4, 7, 11, 5, 4, 4, 2)) +names(finalDigits) <- 0:9 +

This creates a table object containing counts of imaginary final +digits.

Looking at this data:

-finalDigits
-#>  0  1  2  3  4  5  6  7  8  9 
-#> 11  7  5  4  7 11  5  4  4  2
-prop.table(finalDigits) * 100
-#>         0         1         2         3         4         5         6         7 
-#> 18.333333 11.666667  8.333333  6.666667 11.666667 18.333333  8.333333  6.666667 
-#>         8         9 
-#>  6.666667  3.333333
-barplot(finalDigits, xlab = "Final digit", ylab = "Frequency") 
-abline(h = sum(finalDigits) / 10, lty = 3)
+finalDigits +#> 0 1 2 3 4 5 6 7 8 9 +#> 11 7 5 4 7 11 5 4 4 2 +prop.table(finalDigits) * 100 +#> 0 1 2 3 4 5 6 7 +#> 18.333333 11.666667 8.333333 6.666667 11.666667 18.333333 8.333333 6.666667 +#> 8 9 +#> 6.666667 3.333333 +barplot(finalDigits, xlab = "Final digit", ylab = "Frequency") +abline(h = sum(finalDigits) / 10, lty = 3)

-

There is a marked digit preference for zero and five (see figure above). The Chi-squared test:

+

There is a marked digit preference for zero and five (see figure +above). The Chi-squared test:

-chisq.test(finalDigits)
+chisq.test(finalDigits)

returns:

-
#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  finalDigits
-#> X-squared = 13.667, df = 9, p-value = 0.1347
-

In this example the Chi-squared test has failed to detect marked digit preference. This is a false negative test result. The failure of the Chi-squared test in this example is due to the small number of observations (i.e. n = 60) used in the analysis.

-

A tabular and graphical analysis was required to identify the digit preference problem in this example.

-

We will usually be working with large sample sizes. This can bring the problem of false positives.

+
#> 
+#>  Chi-squared test for given probabilities
+#> 
+#> data:  finalDigits
+#> X-squared = 13.667, df = 9, p-value = 0.1347
+

In this example the Chi-squared test has failed to detect marked +digit preference. This is a false negative test result. The +failure of the Chi-squared test in this example is due to the small +number of observations (i.e. n = 60) used in the analysis.

+

A tabular and graphical analysis was required to identify the digit +preference problem in this example.

+

We will usually be working with large sample sizes. This can bring +the problem of false positives.

We will generate some data:

-set.seed(3)
-finalDigits <- sample(x = 0:9, size = 1000, replace = TRUE)
-

These data will approximate the properties of a set of true uniformly random numbers.

-

Any digit preference that we might observe in these data is due solely to chance.

+set.seed(3) +finalDigits <- sample(x = 0:9, size = 1000, replace = TRUE) +

These data will approximate the properties of a set of true uniformly +random numbers.

+

Any digit preference that we might observe in these data is due +solely to chance.

The generated data appear to exhibit some digit preference:

-table(finalDigits)
-#> finalDigits
-#>   0   1   2   3   4   5   6   7   8   9 
-#> 102 104  96  88 103 115  91  86 105 110
-prop.table(fullTable(finalDigits)) * 100
-#>    0    1    2    3    4    5    6    7    8    9 
-#> 10.2 10.4  9.6  8.8 10.3 11.5  9.1  8.6 10.5 11.0
-barplot(fullTable(finalDigits), xlab = "Final digit", ylab = "Frequency") 
-abline(h = sum(fullTable(finalDigits)) / 10, lty = 3)
+table(finalDigits) +#> finalDigits +#> 0 1 2 3 4 5 6 7 8 9 +#> 102 104 96 88 103 115 91 86 105 110 +prop.table(fullTable(finalDigits)) * 100 +#> 0 1 2 3 4 5 6 7 8 9 +#> 10.2 10.4 9.6 8.8 10.3 11.5 9.1 8.6 10.5 11.0 +barplot(fullTable(finalDigits), xlab = "Final digit", ylab = "Frequency") +abline(h = sum(fullTable(finalDigits)) / 10, lty = 3)

-

but this digit preference is not especially marked. The Chi-squared test:

+

but this digit preference is not especially marked. The Chi-squared +test:

-chisq.test(fullTable(finalDigits))
+chisq.test(fullTable(finalDigits))

yields:

-
#> 
-#>  Chi-squared test for given probabilities
-#> 
-#> data:  fullTable(finalDigits)
-#> X-squared = 8.16, df = 9, p-value = 0.5181
+
#> 
+#>  Chi-squared test for given probabilities
+#> 
+#> data:  fullTable(finalDigits)
+#> X-squared = 8.16, df = 9, p-value = 0.5181

which suggests significant digit preference.

-

This is a false positive result because the generated data is constrained to be uniformly random and any digit preference that we observed is due solely to chance.

-

The failure of the Chi-squared test in this example is due to the test mistaking random variation for digit preference is, in part, due to the use of a large (i.e. \(n ~ = ~ 1000\)) number of observations.

-

It is also important to note that any test with a p < 0.05 significance threshold will generate a positive result in 1 in 20 tests with data exhibiting nothing but random variation. All tests with a p < 0.05 significance threshold have a 5% false positive rate.

+

This is a false positive result because the generated data +is constrained to be uniformly random and any digit preference that we +observed is due solely to chance.

+

The failure of the Chi-squared test in this example is due to the +test mistaking random variation for digit preference is, in part, due to +the use of a large (i.e. \(n ~ = ~ +1000\)) number of observations.

+

It is also important to note that any test with a p < +0.05 significance threshold will generate a positive result in 1 in +20 tests with data exhibiting nothing but random variation. All tests +with a p < 0.05 significance threshold have a 5% false +positive rate.

-
-

-Avoiding false positives using the digit preference score

-

The problem of false-positives can be addressed by using a summary measure that takes the effect of sample size into account. A widely used method is the digit preference score (DPS). The DPS was developed by the WHO for the MONICA project:

-

http://www.thl.fi/publications/monica/bp/bpqa.htm

-

The DPS corrects the Chi-squared statistic (\(\chi ^ 2\)) for the sample size (n) and the degrees of freedom (df) of the test:

-

\[ DPS ~ = ~ 100 ~ \times ~ \sqrt{\frac{\chi ^ 2}{n ~ \times ~ df}} \]

+
+

Avoiding false positives using the digit preference score +

+

The problem of false-positives can be addressed by using a summary +measure that takes the effect of sample size into account. A widely used +method is the digit preference score (DPS). The DPS was +developed by the WHO for the MONICA project:

+

http://www.thl.fi/publications/monica/bp/bpqa.htm

+

The DPS corrects the Chi-squared statistic (\(\chi ^ 2\)) for the sample size +(n) and the degrees of freedom (df) of the test:

+

\[ DPS ~ = ~ 100 ~ \times ~ +\sqrt{\frac{\chi ^ 2}{n ~ \times ~ df}} \]

This has the effect of “desensitising” the Chi-squared test.

-

The DPS can be used with anthropometric data from all types surveys and may also be applied to clinical data. A low DPS value indicates little or no digit preference. A high DPS value indicates considerable digit preference.

+

The DPS can be used with anthropometric data from all types surveys +and may also be applied to clinical data. A low DPS value indicates +little or no digit preference. A high DPS value indicates considerable +digit preference.

Guideline values for DPS are shown in table below.

- +
@@ -442,7 +490,7 @@

Guideline thresholds for the DPS
-0 ≤ DPS < 8 +0 ≤ DPS Excellent @@ -450,7 +498,7 @@

-8 ≤ DPS < 12 +8 ≤ DPS Good @@ -474,207 +522,259 @@

-

The NiPN data quality toolkit provides the R language function digitPreference() for calculating the DPS. Applying this function to the example data:

+

The NiPN data quality toolkit provides the R language function +digitPreference() for calculating the DPS. Applying this +function to the example data:

-digitPreference(finalDigits, digits = 0)
+digitPreference(finalDigits, digits = 0)

yields:

-
#> 
-#>  Digit Preference Score
-#> 
-#> data:    finalDigits
-#> Digit Preference Score (DPS) = 3.01 (Excellent)
-

which is consistent with there being little or no digit preference in the example data.

-

The output of the digitPreference() function can be saved for later use:

+
#> 
+#>  Digit Preference Score
+#> 
+#> data:    finalDigits
+#> Digit Preference Score (DPS) = 3.01 (Excellent)
+

which is consistent with there being little or no digit preference in +the example data.

+

The output of the digitPreference() function can be +saved for later use:

-dpsResults <- digitPreference(finalDigits, digits = 0)
-

The saved output contains the DPS value and frequency tables of the final digits (counts and percentages). These can be accessed using:

+dpsResults <- digitPreference(finalDigits, digits = 0)
+

The saved output contains the DPS value and frequency tables of the +final digits (counts and percentages). These can be accessed using:

-dpsResults$dps 
-#> [1] 3.01
-dpsResults$tab 
-#> finalDigits
-#>   0   1   2   3   4   5   6   7   8   9 
-#> 102 104  96  88 103 115  91  86 105 110
-dpsResults$pct 
-#> finalDigits
-#>    0    1    2    3    4    5    6    7    8    9 
-#> 10.2 10.4  9.6  8.8 10.3 11.5  9.1  8.6 10.5 11.0
-dpsResults$dpsClass
-#> SMART DPS Class 
-#>     "Excellent"
+dpsResults$dps +#> [1] 3.01 +dpsResults$tab +#> finalDigits +#> 0 1 2 3 4 5 6 7 8 9 +#> 102 104 96 88 103 115 91 86 105 110 +dpsResults$pct +#> finalDigits +#> 0 1 2 3 4 5 6 7 8 9 +#> 10.2 10.4 9.6 8.8 10.3 11.5 9.1 8.6 10.5 11.0 +dpsResults$dpsClass +#> SMART DPS Class +#> "Excellent"

The saved results may also be plotted:

-plot(dpsResults, main = "finalDigit example data")
+plot(dpsResults, main = "finalDigit example data")

The resulting plot is shown below.

-

We will now practice using the digitPreference() function on survey data.

+

We will now practice using the digitPreference() +function on survey data.

We will start by retrieving some survey data:

-svy <- read.table("dp.ex01.csv", header = TRUE, sep = ",")
-

The file dp.ex01.csv is a comma-separated-value (CSV) file containing anthropometric data for a single state from a DHS survey of a West African country.

+svy <- read.table("dp.ex01.csv", header = TRUE, sep = ",") +

The file dp.ex01.csv is a comma-separated-value +(CSV) file containing anthropometric data for a single state from a DHS +survey of a West African country.

The first few records in this dataset can be seen using:

-head(svy)
+head(svy)

This returns:

-
#>   psu age sex   wt   ht oedema
-#> 1 330  14   1  5.0 65.6      2
-#> 2 330  54   2 12.1 99.0      2
-#> 3 330  25   1  8.9 59.5      2
-#> 4 330  52   1 14.6 98.0      2
-#> 5 330  43   1 10.1 99.1      2
-#> 6 330   7   1  4.0 58.1      2
-

The two variables of interest are wt (weight) and ht (height).

-

We can examine digit preference in the variable for weight (wt) using:

+
#>   psu age sex   wt   ht oedema
+#> 1 330  14   1  5.0 65.6      2
+#> 2 330  54   2 12.1 99.0      2
+#> 3 330  25   1  8.9 59.5      2
+#> 4 330  52   1 14.6 98.0      2
+#> 5 330  43   1 10.1 99.1      2
+#> 6 330   7   1  4.0 58.1      2
+

The two variables of interest are wt (weight) and +ht (height).

+

We can examine digit preference in the variable for weight +(wt) using:

-digitPreference(svy$wt, digits = 1)
+digitPreference(svy$wt, digits = 1)

which returns:

-
#> 
-#>  Digit Preference Score
-#> 
-#> data:    svy$wt
-#> Digit Preference Score (DPS) = 11.86 (Good)
+
#> 
+#>  Digit Preference Score
+#> 
+#> data:    svy$wt
+#> Digit Preference Score (DPS) = 11.86 (Good)

We can plot digit preference using:

-plot(digitPreference(svy$wt, digits = 1), main = "Weight")
+plot(digitPreference(svy$wt, digits = 1), main = "Weight")

The resulting plot is shown below.

-

The weight data shows some digit preference and would be classified as “Good” using the classifications shown in the table above.

-

We can examine digit preference in the variable for height (ht) using:

+

The weight data shows some digit preference and would be classified +as “Good” using the classifications shown in the table above.

+

We can examine digit preference in the variable for height +(ht) using:

-digitPreference(svy$ht, digits = 1) 
-#> 
-#>  Digit Preference Score
-#> 
-#> data:    svy$ht
-#> Digit Preference Score (DPS) = 22.77 (Problematic)
-plot(digitPreference(svy$ht, digits = 1), main = "Height")
+digitPreference(svy$ht, digits = 1) +#> +#> Digit Preference Score +#> +#> data: svy$ht +#> Digit Preference Score (DPS) = 22.77 (Problematic) +plot(digitPreference(svy$ht, digits = 1), main = "Height")

-

The DPS value (22.77) and the DPS plot (above) show considerable digit preference in the height (ht) variable. This would be classified as “Problematic” using the classifications shown in table above.

-

Note that we specified digits = 1 when we used the digitPreference() function for the weight and height data in the example DHS data. This is because these variables are measured and recorded to one decimal place.

-

If we were using the digitPreference() function with MUAC data that is measured and recorded as whole numbers (i.e. with no decimal places) then we should specify digits = 0. For example:

+

The DPS value (22.77) and the DPS plot (above) show considerable +digit preference in the height (ht) variable. This +would be classified as “Problematic” using the classifications shown in +table above.

+

Note that we specified digits = 1 when we used the +digitPreference() function for the weight and height data +in the example DHS data. This is because these variables are measured +and recorded to one decimal place.

+

If we were using the digitPreference() function with +MUAC data that is measured and recorded as whole numbers (i.e. with no +decimal places) then we should specify digits = 0. For +example:

-svy <- read.table("dp.ex02.csv", header = TRUE, sep = ",")
-

The file dp.ex02.csv is a comma-separated-value (CSV) file containing anthropometric data from a SMART survey in Kabul, Afghanistan.

+svy <- read.table("dp.ex02.csv", header = TRUE, sep = ",") +

The file dp.ex02.csv is a comma-separated-value +(CSV) file containing anthropometric data from a SMART survey in Kabul, +Afghanistan.

The first few records in this dataset can be seen using:

-head(svy)
+head(svy)

which returns:

-
#>   psu age sex weight height muac oedema
-#> 1   1   6   1    7.3   65.0  146      2
-#> 2   1  42   2   12.5   89.5  156      2
-#> 3   1  23   1   10.6   78.1  149      2
-#> 4   1  18   1   12.8   81.5  160      2
-#> 5   1  52   1   12.1   87.3  152      2
-#> 6   1  36   2   16.9   93.0  190      2
-

The variable of interest is muac (MUAC). This variable is measured and recorded in whole millimetres.

+
#>   psu age sex weight height muac oedema
+#> 1   1   6   1    7.3   65.0  146      2
+#> 2   1  42   2   12.5   89.5  156      2
+#> 3   1  23   1   10.6   78.1  149      2
+#> 4   1  18   1   12.8   81.5  160      2
+#> 5   1  52   1   12.1   87.3  152      2
+#> 6   1  36   2   16.9   93.0  190      2
+

The variable of interest is muac (MUAC). This +variable is measured and recorded in whole millimetres.

We can examine digit preference in the MUAC variable using:

-digitPreference(svy$muac, digits = 0) 
-#> 
-#>  Digit Preference Score
-#> 
-#> data:    svy$muac
-#> Digit Preference Score (DPS) = 13.08 (Acceptable)
-plot(digitPreference(svy$muac, digits = 0), main = "MUAC")
+digitPreference(svy$muac, digits = 0) +#> +#> Digit Preference Score +#> +#> data: svy$muac +#> Digit Preference Score (DPS) = 13.08 (Acceptable) +plot(digitPreference(svy$muac, digits = 0), main = "MUAC")

-

The DPS value (13.08) and the DPS plot (above) show considerable digit preference and would be classified as “Acceptable” using the classifications shown in the table above.

+

The DPS value (13.08) and the DPS plot (above) show considerable +digit preference and would be classified as “Acceptable” using the +classifications shown in the table above.

-
-

-Some warnings

-

The material presented here has assumed that data are recorded with a fixed precision (e.g. one decimal place for weight and height, no decimal places for MUAC). It may be the case that data are recorded with mixed precision. For example, the weights of younger children may be measured using “baby scales” and recorded to the nearest 10 g (i.e. to two decimal places) and the weights of older children measured using “hanging scales” and recorded to the nearest 100 g (i.e. to one decimal place). These sorts of situations can be difficult to handle automatically since (e.g.) 3.1 and 3.10 are the same number and both will be stored in the same way. The easiest approach is to treat the data as two separate datasets when examining digit preference.

-

Care should be taken to ensure that you do not mistake the limitations of the measuring instrument for digit preference. For example, some designs of MUAC tape can only return measurements with an even number for the final digit. In this case you should never see MUAC measurements with 1, 3, 5, 7, or 9 as the final digit. This limitation of the instrument would look like digit preference. The digitPreference() function can handle this situation.

+
+

Some warnings +

+

The material presented here has assumed that data are recorded with a +fixed precision (e.g. one decimal place for weight and height, no +decimal places for MUAC). It may be the case that data are recorded with +mixed precision. For example, the weights of younger children may be +measured using “baby scales” and recorded to the nearest 10 g (i.e. to +two decimal places) and the weights of older children measured using +“hanging scales” and recorded to the nearest 100 g (i.e. to one decimal +place). These sorts of situations can be difficult to handle +automatically since (e.g.) 3.1 and 3.10 are the same number and both +will be stored in the same way. The easiest approach is to treat the +data as two separate datasets when examining digit preference.

+

Care should be taken to ensure that you do not mistake the +limitations of the measuring instrument for digit preference. For +example, some designs of MUAC tape can only return measurements with an +even number for the final digit. In this case you should never see MUAC +measurements with 1, 3, 5, 7, or 9 as +the final digit. This limitation of the instrument would look like digit +preference. The digitPreference() function can handle this +situation.

We will retrieve a dataset:

-svy <- read.table("dp.ex03.csv", header = TRUE, sep = ",") 
-head(svy)
-
#>   age sex weight height muac oedema
-#> 1  36   2   12.4   86.9  150      2
-#> 2  39   2   10.9   83.5  146      2
-#> 3  29   1   11.6   90.6  138      2
-#> 4  47   2   14.6   95.5  170      2
-#> 5  16   1   10.4   78.9  154      2
-#> 6  23   1    8.9   80.8  146      2
-

The file dp.ex03.csv is a comma-separated-value (CSV) file containing anthropometric data for a sample of children living in a refugee camp in a West African country.

+svy <- read.table("dp.ex03.csv", header = TRUE, sep = ",") +head(svy)
+
#>   age sex weight height muac oedema
+#> 1  36   2   12.4   86.9  150      2
+#> 2  39   2   10.9   83.5  146      2
+#> 3  29   1   11.6   90.6  138      2
+#> 4  47   2   14.6   95.5  170      2
+#> 5  16   1   10.4   78.9  154      2
+#> 6  23   1    8.9   80.8  146      2
+

The file dp.ex03.csv is a comma-separated-value +(CSV) file containing anthropometric data for a sample of children +living in a refugee camp in a West African country.

MUAC was measured using a “numbers in boxes” design MUAC tape:

-

-

There can only be even numbers in the final digit when this type of MUAC tape is used.

+

+

There can only be even numbers in the final digit when this type of +MUAC tape is used.

We should check this:

-table(svy$muac)
+table(svy$muac)

This returns:

-
#> 
-#> 108 114 118 120 122 124 126 128 130 132 134 136 138 140 142 144 146 148 150 152 
-#>   1   1   3   3   2   6   5   5  21   8  16  23  20  16  32  26  24  22  16  25 
-#> 154 156 158 160 162 164 166 168 170 174 176 178 
-#>  16  14  19   8   7   7   9   3  11   2   2   1
-

There are only even numbers. Any odd number would be a recording error or a data-entry error.

-

We can examine digit preference in these data using the digitPreference() function:

+
#> 
+#> 108 114 118 120 122 124 126 128 130 132 134 136 138 140 142 144 146 148 150 152 
+#>   1   1   3   3   2   6   5   5  21   8  16  23  20  16  32  26  24  22  16  25 
+#> 154 156 158 160 162 164 166 168 170 174 176 178 
+#>  16  14  19   8   7   7   9   3  11   2   2   1
+

There are only even numbers. Any odd number would be a recording +error or a data-entry error.

+

We can examine digit preference in these data using the +digitPreference() function:

-digitPreference(svy$muac, digits = 0)
+digitPreference(svy$muac, digits = 0)

This returns:

-
#> 
-#>  Digit Preference Score
-#> 
-#> data:    svy$muac
-#> Digit Preference Score (DPS) = 33.34 (Problematic)
-

This is misleading because the digitPreference() function assumes that all possible final digits (i.e. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) should be present. This is not the case in the example data.

+
#> 
+#>  Digit Preference Score
+#> 
+#> data:    svy$muac
+#> Digit Preference Score (DPS) = 33.34 (Problematic)
+

This is misleading because the digitPreference() +function assumes that all possible final digits (i.e. 0, 1, 2, +3, 4, 5, 6, 7, 8, 9) should be present. This is not the case in +the example data.

We can examine this using:

-digitPreference(svy$muac, digits = 0)$tab
+digitPreference(svy$muac, digits = 0)$tab

which returns:

-
#> svy$muac
-#>  0  1  2  3  4  5  6  7  8  9 
-#> 75  0 74  0 74  0 77  0 74  0
-

We can use the values parameter of the digitPreference() to specify the values that are allowed in the final digit:

+
#> svy$muac
+#>  0  1  2  3  4  5  6  7  8  9 
+#> 75  0 74  0 74  0 77  0 74  0
+

We can use the values parameter of the +digitPreference() to specify the values that are allowed in +the final digit:

-digitPreference(svy$muac, digits = 0, values = c(0, 2, 4, 6, 8)) 
+digitPreference(svy$muac, digits = 0, values = c(0, 2, 4, 6, 8))

This returns:

-
#> 
-#>  Digit Preference Score
-#> 
-#> data:    svy$muac
-#> Digit Preference Score (DPS) = 0.78 (Excellent)
-

The DPS has moved from 33.34 (“Problematic”) to 0.78 (“Excellent”).

-

We can tabulate and plot the frequency of final digits in the muac variable:

+
#> 
+#>  Digit Preference Score
+#> 
+#> data:    svy$muac
+#> Digit Preference Score (DPS) = 0.78 (Excellent)
+

The DPS has moved from 33.34 (“Problematic”) to 0.78 +(“Excellent”).

+

We can tabulate and plot the frequency of final digits in the +muac variable:

-dpsResults <- digitPreference (svy$muac, digits = 0, values = c(0, 2, 4, 6, 8)) 
-dpsResults$tab
-#> svy$muac
-#>  0  2  4  6  8 
-#> 75 74 74 77 74
-dpsResults$pct
-#> svy$muac
-#>    0    2    4    6    8 
-#> 20.1 19.8 19.8 20.6 19.8
-plot(dpsResults)
+dpsResults <- digitPreference (svy$muac, digits = 0, values = c(0, 2, 4, 6, 8)) +dpsResults$tab +#> svy$muac +#> 0 2 4 6 8 +#> 75 74 74 77 74 +dpsResults$pct +#> svy$muac +#> 0 2 4 6 8 +#> 20.1 19.8 19.8 20.6 19.8 +plot(dpsResults)

- - - - + -
+ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-11-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-11-1.png index 6e4836d..296d9ac 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-11-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-11-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-15-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-15-1.png index fec5f0f..2dab679 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-15-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-15-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-17-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-17-1.png index 5c1eff1..73fdafb 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-17-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-22-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-22-1.png index 674b6cb..df45bb4 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-22-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-26-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-26-1.png index e7d66d8..d0d37a2 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-26-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-26-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-35-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-35-1.png index 64fe434..235181d 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-35-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-35-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-43-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-43-1.png index d2c1ca5..87a8312 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-43-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-43-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-44-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-44-1.png index 5409461..258612b 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-44-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-44-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-49-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-49-1.png index 5feeab4..64403e0 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-49-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-49-1.png differ diff --git a/docs/articles/dp_files/figure-html/unnamed-chunk-61-1.png b/docs/articles/dp_files/figure-html/unnamed-chunk-61-1.png index 1814a17..7bb08c6 100644 Binary files a/docs/articles/dp_files/figure-html/unnamed-chunk-61-1.png and b/docs/articles/dp_files/figure-html/unnamed-chunk-61-1.png differ diff --git a/docs/articles/flagging.html b/docs/articles/flagging.html index b13ab8d..4d2bff5 100644 --- a/docs/articles/flagging.html +++ b/docs/articles/flagging.html @@ -4,7 +4,8 @@ - + + Identifying outliers using flags • nipnTK @@ -12,144 +13,125 @@ - - - + + + - - + + - -
-
- + +
-
- +
#>   psu child age sex weight height muac oedema   haz   waz   whz flag
+#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    0
+#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93    0
+#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    0
+#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57    0
+#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61    0
+#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82    0
+

This can be translated as “if HAZ is not missing and HAZ is below +-6 or HAZ is above +6 then add 1 to the flag variable else leave the +flag variable unchanged”.

+

Be careful when using the \(<\) +comparison operator with negative numbers. Always insert a space between +the \(<\) and \(–\) characters. R interprets \(<-\) as an assignment operator and may +produce unexpected and unwanted results without issuing a warning or +error message.

Here we apply the WHO flagging criteria to the WHZ index:

-svy$flag <- ifelse(!is.na(svy$whz) & (svy$whz < - 5 | svy$whz > 5), svy$flag + 2, svy$flag)
-
#>   psu child age sex weight height muac oedema   haz   waz   whz flag
-#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
-#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93    0
-#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    0
-#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57    0
-#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61    0
-#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82    0
+svy$flag <- ifelse(!is.na(svy$whz) & (svy$whz < - 5 | svy$whz > 5), svy$flag + 2, svy$flag)
+
#>   psu child age sex weight height muac oedema   haz   waz   whz flag
+#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
+#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93    0
+#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    0
+#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57    0
+#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61    0
+#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82    0

Here we apply the WHO flagging criteria to the WAZ index:

-svy$flag <- ifelse(!is.na(svy$waz) & (svy$waz < - 6 | svy$waz > 5), svy$flag + 4, svy$flag)
-
#>   psu child age sex weight height muac oedema   haz   waz   whz flag
-#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
-#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93    0
-#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    0
-#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57    0
-#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61    0
-#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82    0
-

Note that each time we apply a flagging criteria we increase the value of the flagging variable by the next power of two when a problem is detected:

-

We started with zero

-

Then we added \(2 ^ 0\) (i.e. 1) if HAZ was out of range.

-

Then we added \(2 ^ 1\) (i.e. 2) if WHZ was out of range.

-

Then we added \(2 ^ 2\) (i.e. 4) if WAZ was out of range.

-

If we had another index then we would use \(2 ^ 3\) (i.e. 8) to flag a problem in that index.

-

The advantage of using this coding scheme is that it compactly codes all possible combinations of problems in a single variable (see table below).

-

There are a number of flagged records in the example dataset. This:

-
-table(svy$flag)
+svy$flag <- ifelse(!is.na(svy$waz) & (svy$waz < - 6 | svy$waz > 5), svy$flag + 4, svy$flag)
+
#>   psu child age sex weight height muac oedema   haz   waz   whz flag
+#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
+#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93    0
+#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    0
+#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57    0
+#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61    0
+#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82    0
+

Note that each time we apply a flagging criteria we increase the +value of the flagging variable by the next power of two when a problem +is detected:

+
We started with zero
+
+Then we added $2 ^ 0$ (i.e. 1) if HAZ was out of range. 
+
+Then we added $2 ^ 1$ (i.e. 2) if WHZ was out of range. 
+
+Then we added $2 ^ 2$ (i.e. 4) if WAZ was out of range.
+

If we had another index then we would use \(2 ^ 3\) (i.e. 8) to flag a problem in that +index.

+

The advantage of using this coding scheme is that it compactly codes +all possible combinations of problems in a single variable (see table +below).

+

There are a number of flagged records in the example dataset. +This:

+
+table(svy$flag)

returns:

-
#> 
-#>   0   1   2   3   5   6 
-#> 751   9  12   9   2   3
-

This table shows the relative frequency of detected problems. See table below to find the meaning of each of the codes.

+
#> 
+#>   0   1   2   3   5   6 
+#> 751   9  12   9   2   3
+

This table shows the relative frequency of detected problems. See +table below to find the meaning of each of the codes.

 

- +
@@ -591,116 +663,138 @@

Flagging codes based on powers of two and their meanings

 

The number of flagged records can be found using:

-
-table(svy$flag != 0)["TRUE"]
+
+table(svy$flag != 0)["TRUE"]

which returns:

-
#> TRUE 
-#>   35
+
#> TRUE 
+#>   35

The proportion of records that are flagged can be found using:

-
-prop.table(table(svy$flag != 0))["TRUE"]
+
+prop.table(table(svy$flag != 0))["TRUE"]

This returns:

-
#>       TRUE 
-#> 0.04452926
+
#>       TRUE 
+#> 0.04452926

About 4.45% of records are flagged.

-

Note that missing values are not flagged. It can be useful to check missing values to see if there are missing component measurements or if a component measurement is out of range for the calculation of index values (e.g. WAZ is only calculated for children aged ten years or younger). This issue can be explored by selection and listing. For example:

-
-svy[is.na(svy$whz), c("weight", "height", "whz")]
+

Note that missing values are not flagged. It can be useful to check +missing values to see if there are missing component measurements or if +a component measurement is out of range for the calculation of index +values (e.g. WAZ is only calculated for children aged ten years or +younger). This issue can be explored by selection and listing. For +example:

+
+svy[is.na(svy$whz), c("weight", "height", "whz")]

This returns:

-
#>   weight height whz
-#> 8    8.1     NA  NA
-

There is one missing value for whz in record 8.This is due to a missing value for height (shown as NA). and haz will also be missing. It may be possible to fix this issue if the missing data are available on paper forms.

+
#>   weight height whz
+#> 8    8.1     NA  NA
+

There is one missing value for whz in record +8.This is due to a missing value for +height (shown as NA). and +haz will also be missing. It may be possible to fix +this issue if the missing data are available on paper forms.

Flagging has a dual role:

    -
  1. It is a data-checking tool. If you have access to data collection forms you will be often able to check records and fix data-entry errors in the data.

  2. -
  3. It is a measure of data-quality. Flagged records can indicate problems with measurement, recording, data-entry, and data-checking. The proportion of flagged records in a dataset should, ideally, be below about 2.5%. SMART guidelines consider proportions above 7.5% to be problematic. We found that 4.45% of records in the example dataset were flagged. The data are of acceptable quality.

  4. +
  5. It is a data-checking tool. If you have access to data collection +forms you will be often able to check records and fix data-entry errors +in the data.

  6. +
  7. It is a measure of data-quality. Flagged records can indicate +problems with measurement, recording, data-entry, and data-checking. The +proportion of flagged records in a dataset should, ideally, be below +about 2.5%. SMART guidelines consider proportions above 7.5% to be +problematic. We found that 4.45% of records in the example dataset were +flagged. The data are of acceptable quality.

We can use:

-
-svy[svy$flag != 0, ]
-#>     psu child age sex weight height muac oedema   haz   waz   whz flag
-#> 1     1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
-#> 29    1    29  24   2   16.3  107.3  155      2  6.69  2.69 -0.82    1
-#> 32    2     1  12   1    6.1   99.4  112      2  9.95 -4.02 -9.18    3
-#> 35    2     4  24   2    6.8   65.5  128      2 -6.27 -4.30 -0.63    1
-#> 88    3    30  24   2   16.9  107.5  158      2  6.75  2.95 -0.47    1
-#> 106   4    18  36   1   13.4   65.7  152      2 -8.20 -0.56  7.64    3
-#> 174   7     3  36   2    6.8   66.6  134      2 -7.47 -5.35 -1.01    1
-#> 198   8     1  27   2    5.5   66.0  112      2 -6.59 -5.92 -3.27    1
-#> 280  11     7  24   1    6.7   81.7  140      2 -1.77 -4.86 -5.63    2
-#> 286  11    13  48   1    9.4   77.3  146      2 -6.21 -4.25 -0.69    1
-#> 292  11    19  12   1   12.9   92.3  152      2  6.97  2.68 -0.50    1
-#> 307  12     3  36   1    7.5   90.0  130      2 -1.64 -4.99 -6.42    2
-#> 350  14     1  20   1    5.7   77.8  142      2 -2.27 -5.49 -6.47    2
-#> 352  14     3  48   1    6.5   80.7  140      2 -5.40 -6.22 -5.74    6
-#> 368  14    19  48   1   13.4   66.3  144      2 -8.83 -1.58  7.33    3
-#> 399  15    21  36   1   14.3   66.0  154      2 -8.12 -0.02  8.58    3
-#> 400  15    22  48   1   14.5   68.0  152      2 -8.42 -0.95  7.80    3
-#> 405  16     4  24   2    7.8   65.0  145      2 -6.42 -3.27  1.04    1
-#> 406  16     5  12   1    7.8   98.0  138      2  9.36 -1.93 -7.23    3
-#> 408  16     7  48   1    8.0   77.0  128      2 -6.28 -5.20 -2.66    1
-#> 432  17     3   6   1    7.9   98.4  138      2 14.38 -0.04 -7.18    3
-#> 433  17     4  48   2    8.3   94.9  136      2 -1.82 -4.79 -5.63    2
-#> 490  19     1  12   2    5.3   72.0  152      2 -0.78 -4.27 -5.30    2
-#> 591  22    24  36   1   14.0   69.0  152      2 -7.31 -0.20  6.77    3
-#> 594  23     1  36   1    5.4   80.0  140      2 -4.34 -6.66 -7.27    6
-#> 595  23     2  36   1    5.9   72.0  114      2 -6.50 -6.26 -4.96    5
-#> 596  23     3  24   1    6.3   77.0  130      2 -3.31 -5.24 -5.38    2
-#> 599  23     6  36   1    6.5   80.0  130      2 -4.34 -5.79 -5.61    2
-#> 616  23    23  36   1   16.0   74.0  144      2 -5.96  0.90  6.82    2
-#> 640  25     1  12   2    6.3   99.3  110      2  9.82 -2.96 -8.25    3
-#> 641  25     2  48   2    6.7   85.0  140      2 -4.12 -5.90 -5.83    2
-#> 671  26     1  48   1    5.3   95.0  135      2 -1.99 -7.03 -9.71    6
-#> 690  26    20  36   1   16.0   79.0  162      2 -4.61  0.90  5.34    2
-#> 715  28     4  36   2    7.7  103.0  114      2  2.09 -4.60 -7.31    2
-#> 757  30     1  24   1    5.5   68.6  106      2 -6.06 -6.01 -4.76    5
+
+svy[svy$flag != 0, ]
+#>     psu child age sex weight height muac oedema   haz   waz   whz flag
+#> 1     1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
+#> 29    1    29  24   2   16.3  107.3  155      2  6.69  2.69 -0.82    1
+#> 32    2     1  12   1    6.1   99.4  112      2  9.95 -4.02 -9.18    3
+#> 35    2     4  24   2    6.8   65.5  128      2 -6.27 -4.30 -0.63    1
+#> 88    3    30  24   2   16.9  107.5  158      2  6.75  2.95 -0.47    1
+#> 106   4    18  36   1   13.4   65.7  152      2 -8.20 -0.56  7.64    3
+#> 174   7     3  36   2    6.8   66.6  134      2 -7.47 -5.35 -1.01    1
+#> 198   8     1  27   2    5.5   66.0  112      2 -6.59 -5.92 -3.27    1
+#> 280  11     7  24   1    6.7   81.7  140      2 -1.77 -4.86 -5.63    2
+#> 286  11    13  48   1    9.4   77.3  146      2 -6.21 -4.25 -0.69    1
+#> 292  11    19  12   1   12.9   92.3  152      2  6.97  2.68 -0.50    1
+#> 307  12     3  36   1    7.5   90.0  130      2 -1.64 -4.99 -6.42    2
+#> 350  14     1  20   1    5.7   77.8  142      2 -2.27 -5.49 -6.47    2
+#> 352  14     3  48   1    6.5   80.7  140      2 -5.40 -6.22 -5.74    6
+#> 368  14    19  48   1   13.4   66.3  144      2 -8.83 -1.58  7.33    3
+#> 399  15    21  36   1   14.3   66.0  154      2 -8.12 -0.02  8.58    3
+#> 400  15    22  48   1   14.5   68.0  152      2 -8.42 -0.95  7.80    3
+#> 405  16     4  24   2    7.8   65.0  145      2 -6.42 -3.27  1.04    1
+#> 406  16     5  12   1    7.8   98.0  138      2  9.36 -1.93 -7.23    3
+#> 408  16     7  48   1    8.0   77.0  128      2 -6.28 -5.20 -2.66    1
+#> 432  17     3   6   1    7.9   98.4  138      2 14.38 -0.04 -7.18    3
+#> 433  17     4  48   2    8.3   94.9  136      2 -1.82 -4.79 -5.63    2
+#> 490  19     1  12   2    5.3   72.0  152      2 -0.78 -4.27 -5.30    2
+#> 591  22    24  36   1   14.0   69.0  152      2 -7.31 -0.20  6.77    3
+#> 594  23     1  36   1    5.4   80.0  140      2 -4.34 -6.66 -7.27    6
+#> 595  23     2  36   1    5.9   72.0  114      2 -6.50 -6.26 -4.96    5
+#> 596  23     3  24   1    6.3   77.0  130      2 -3.31 -5.24 -5.38    2
+#> 599  23     6  36   1    6.5   80.0  130      2 -4.34 -5.79 -5.61    2
+#> 616  23    23  36   1   16.0   74.0  144      2 -5.96  0.90  6.82    2
+#> 640  25     1  12   2    6.3   99.3  110      2  9.82 -2.96 -8.25    3
+#> 641  25     2  48   2    6.7   85.0  140      2 -4.12 -5.90 -5.83    2
+#> 671  26     1  48   1    5.3   95.0  135      2 -1.99 -7.03 -9.71    6
+#> 690  26    20  36   1   16.0   79.0  162      2 -4.61  0.90  5.34    2
+#> 715  28     4  36   2    7.7  103.0  114      2  2.09 -4.60 -7.31    2
+#> 757  30     1  24   1    5.5   68.6  106      2 -6.06 -6.01 -4.76    5

to display the flagged records.

This:

-
-svy[svy$flag != 0, c("psu", "child", "flag")]
-#>     psu child flag
-#> 1     1     1    2
-#> 29    1    29    1
-#> 32    2     1    3
-#> 35    2     4    1
-#> 88    3    30    1
-#> 106   4    18    3
-#> 174   7     3    1
-#> 198   8     1    1
-#> 280  11     7    2
-#> 286  11    13    1
-#> 292  11    19    1
-#> 307  12     3    2
-#> 350  14     1    2
-#> 352  14     3    6
-#> 368  14    19    3
-#> 399  15    21    3
-#> 400  15    22    3
-#> 405  16     4    1
-#> 406  16     5    3
-#> 408  16     7    1
-#> 432  17     3    3
-#> 433  17     4    2
-#> 490  19     1    2
-#> 591  22    24    3
-#> 594  23     1    6
-#> 595  23     2    5
-#> 596  23     3    2
-#> 599  23     6    2
-#> 616  23    23    2
-#> 640  25     1    3
-#> 641  25     2    2
-#> 671  26     1    6
-#> 690  26    20    2
-#> 715  28     4    2
-#> 757  30     1    5
+
+svy[svy$flag != 0, c("psu", "child", "flag")]
+#>     psu child flag
+#> 1     1     1    2
+#> 29    1    29    1
+#> 32    2     1    3
+#> 35    2     4    1
+#> 88    3    30    1
+#> 106   4    18    3
+#> 174   7     3    1
+#> 198   8     1    1
+#> 280  11     7    2
+#> 286  11    13    1
+#> 292  11    19    1
+#> 307  12     3    2
+#> 350  14     1    2
+#> 352  14     3    6
+#> 368  14    19    3
+#> 399  15    21    3
+#> 400  15    22    3
+#> 405  16     4    1
+#> 406  16     5    3
+#> 408  16     7    1
+#> 432  17     3    3
+#> 433  17     4    2
+#> 490  19     1    2
+#> 591  22    24    3
+#> 594  23     1    6
+#> 595  23     2    5
+#> 596  23     3    2
+#> 599  23     6    2
+#> 616  23    23    2
+#> 640  25     1    3
+#> 641  25     2    2
+#> 671  26     1    6
+#> 690  26    20    2
+#> 715  28     4    2
+#> 757  30     1    5

produces a more compact list.

-

In the example dataset records are identified using a combination of the psu and child variables.

-

The listed records can be checked and edited (see previous table). Anthropometric indices can then be recalculated and the flagging process repeated until all records that can be fixed have been fixed.

-

Records that cannot be fixed can be censored during analysis. Records are usually censored on an index-by-index basis. For example, an analysis based on WHZ would censor records in which the flag variable is 2, 3, 6, or 7.

+

In the example dataset records are identified using a combination of +the psu and child variables.

+

The listed records can be checked and edited (see previous table). +Anthropometric indices can then be recalculated and the flagging process +repeated until all records that can be fixed have been fixed.

+

Records that cannot be fixed can be censored during analysis. Records +are usually censored on an index-by-index basis. For example, an +analysis based on WHZ would censor records in which the flag variable is +2, 3, 6, or 7.

Table below shows censoring rules for each index:

 

- +
@@ -740,235 +834,263 @@

Censoring rules for each index

 

-

You should be very careful when applying censoring rules. An analysis of prevalence using WHZ, for example, will usually include children with oedema because a commonly used case-definition for acute malnutrition is:

-

\[ \text{WHZ} < -2 ~ \text{or bilateral pitting oedema} \]

-

If you want to use case-definitions that include oedema then you should be careful not to exclude children with oedema when censoring flagged records. For an analysis using WAZ you might want to exclude oedema cases.

+

You should be very careful when applying censoring rules. An analysis +of prevalence using WHZ, for example, will usually include children with +oedema because a commonly used case-definition for acute malnutrition +is:

+

\[ \text{WHZ} < -2 ~ \text{or bilateral +pitting oedema} \]

+

If you want to use case-definitions that include oedema then you +should be careful not to exclude children with oedema when censoring +flagged records. For an analysis using WAZ you might want to exclude +oedema cases.

-
-

-Applying SMART flagging criteria to survey data

-

In the next exercise we will apply SMART flagging criteria to the same survey dataset.

+
+

Applying SMART flagging criteria to survey data +

+

In the next exercise we will apply SMART flagging criteria to the +same survey dataset.

We will retrieve the survey dataset:

-
-svy <- read.table("flag.ex01.csv", header = TRUE, sep = ",")
-
#>   psu child age sex weight height muac oedema   haz   waz   whz
-#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03
-#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93
-#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25
-#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57
-#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61
-#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82
-

and create a column that will contain the flag code and set this to zero (i.e. no flags) for all records:

-
-svy$flag <- 0
-

Applying SMART flagging criteria requires us to first calculate a mean index value:

+
+svy <- read.table("flag.ex01.csv", header = TRUE, sep = ",")
+
#>   psu child age sex weight height muac oedema   haz   waz   whz
+#> 1   1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03
+#> 2   1     2  13   2    6.4   70.4  116      2 -1.83 -3.04 -2.93
+#> 3   1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25
+#> 4   1     4  15   1    7.2   75.4  130      2 -1.48 -3.22 -3.57
+#> 5   1     5  15   1    7.4   70.0  124      2 -3.61 -2.99 -1.61
+#> 6   1     6  18   2    7.7   70.6  130      2 -3.48 -2.40 -0.82
+

and create a column that will contain the flag code and set this to +zero (i.e. no flags) for all records:

-meanHAZ <- mean(svy$haz, na.rm = TRUE)
-

and then to use this mean value to define flagging ranges:

+svy$flag <- 0
+

Applying SMART flagging criteria requires us to first calculate a +mean index value:

-svy$flag <- ifelse(!is.na(svy$haz) & 
-              (svy$haz < (meanHAZ - 3) | svy$haz > (meanHAZ + 3)), 
-              svy$flag + 1, svy$flag)
-

We do this for each index:

+meanHAZ <- mean(svy$haz, na.rm = TRUE)
+

and then to use this mean value to define flagging ranges:

-
-meanWHZ <- mean(svy$whz, na.rm = TRUE)
-
-svy$flag <- ifelse(!is.na(svy$whz) &
-              (svy$whz < (meanWHZ - 3) | svy$whz > (meanWHZ + 3)),
-              svy$flag + 2, svy$flag) 
-
-meanWAZ <- mean(svy$waz, na.rm = TRUE)
-
-svy$flag <- ifelse(!is.na(svy$waz) &
-              (svy$waz < (meanWAZ - 3) | svy$waz > (meanWAZ + 3)),
-              svy$flag + 4, svy$flag)
+svy$flag <- ifelse(!is.na(svy$haz) & + (svy$haz < (meanHAZ - 3) | svy$haz > (meanHAZ + 3)), + svy$flag + 1, svy$flag) +

We do this for each index:

+
+
+meanWHZ <- mean(svy$whz, na.rm = TRUE)
+
+svy$flag <- ifelse(!is.na(svy$whz) &
+              (svy$whz < (meanWHZ - 3) | svy$whz > (meanWHZ + 3)),
+              svy$flag + 2, svy$flag) 
+
+meanWAZ <- mean(svy$waz, na.rm = TRUE)
+
+svy$flag <- ifelse(!is.na(svy$waz) &
+              (svy$waz < (meanWAZ - 3) | svy$waz > (meanWAZ + 3)),
+              svy$flag + 4, svy$flag)

There are a number of flagged records in the example dataset.

This:

-
-table(svy$flag)
+
+table(svy$flag)

returns:

-
#> 
-#>   0   1   2   3   4   5   6   7 
-#> 660  59  11  16   1  19  16   4
-

This table shows the relative frequency of detected problems. See the previous table to find the meaning of each of the codes. The number of flagged records can be found using:

-
-table(svy$flag != 0)["TRUE"]
+
#> 
+#>   0   1   2   3   4   5   6   7 
+#> 660  59  11  16   1  19  16   4
+

This table shows the relative frequency of detected problems. See the +previous table to find the meaning of each of the codes. The number of +flagged records can be found using:

+
+table(svy$flag != 0)["TRUE"]

which returns:

-
#> TRUE 
-#>  126
+
#> TRUE 
+#>  126

The proportion of records that are flagged can be found using:

-
-prop.table(table(svy$flag != 0))["TRUE"]
+
+prop.table(table(svy$flag != 0))["TRUE"]

which returns:

-
#>      TRUE 
-#> 0.1603053
-

About 16% of records are flagged. This is a very high proportion of records flagged.

-

Note how the SMART flagging criteria identify considerably more records (126 records flagged) than the WHO flagging criteria (35 records flagged). In this example the SMART flagging criteria flagged 91 biologically plausible records.

+
#>      TRUE 
+#> 0.1603053
+

About 16% of records are flagged. This is a very high proportion of +records flagged.

+

Note how the SMART flagging criteria identify considerably more +records (126 records flagged) than the WHO flagging criteria (35 records +flagged). In this example the SMART flagging criteria flagged 91 +biologically plausible records.

We can list flagged records using:

-
-svy[svy$flag != 0, ]
-#>     psu child age sex weight height muac oedema   haz   waz   whz flag
-#> 1     1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
-#> 3     1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    1
-#> 15    1    15  36   1   12.3   79.7  144      2 -4.42 -1.27  1.97    3
-#> 28    1    28  48   2   15.8  109.7  146      2  1.62 -0.12 -1.72    1
-#> 29    1    29  24   2   16.3  107.3  155      2  6.69  2.69 -0.82    5
-#> 31    1    31  48   2   18.8  109.9  166      2  1.66  1.10  0.13    1
-#> 32    2     1  12   1    6.1   99.4  112      2  9.95 -4.02 -9.18    3
-#> 34    2     3  24   2    6.5   76.0  108      2 -3.01 -4.61 -4.16    6
-#> 35    2     4  24   2    6.8   65.5  128      2 -6.27 -4.30 -0.63    1
-#> 36    2     5  36   1    7.3   76.0  110      2 -5.42 -5.15 -3.56    5
-#> 42    2    11  12   2    9.9   80.0  150      2  2.32  0.82 -0.21    1
-#> 44    2    13  36   2   10.5   78.0  142      2 -4.48 -2.24  0.87    1
-#> 52    2    21  36   1   12.7   77.5  144      2 -5.01 -1.01  2.77    3
-#> 57    2    26  24   1   15.5   93.7  166      2  2.16  2.13  1.46    5
-#> 59    3     1  18   2    5.7   67.0  110      2 -4.72 -4.72 -3.21    5
-#> 66    3     8  48   2    9.4   79.0  144      2 -5.51 -4.03 -0.57    1
-#> 76    3    18  24   2   12.1   96.0  138      2  3.19  0.42 -1.79    1
-#> 88    3    30  24   2   16.9  107.5  158      2  6.75  2.95 -0.47    5
-#> 89    4     1  26   2    6.6   71.7  114      2 -4.73 -4.74 -2.95    5
-#> 106   4    18  36   1   13.4   65.7  152      2 -8.20 -0.56  7.64    3
-#> 107   4    19  24   1   13.7   97.6  150      2  3.43  1.05 -0.89    1
-#> 122   5     4  24   1    8.0   73.3  130      2 -4.52 -3.61 -1.66    1
-#> 125   5     7  36   2   11.3  106.2  150      2  2.93 -1.63 -4.61    3
-#> 139   5    21  24   2   15.2   82.0  138      2 -1.15  2.18  3.97    6
-#> 154   6    14  24   2   11.9   91.0  148      2  1.64  0.29 -0.91    1
-#> 165   6    25  36   1   14.9  108.0  144      2  3.21  0.31 -2.13    1
-#> 173   7     2  10   2    6.5   76.2  122      2  1.91 -2.23 -4.20    3
-#> 174   7     3  36   2    6.8   66.6  134      2 -7.47 -5.35 -1.01    5
-#> 187   7    16  10   2   11.6   84.3  152      2  5.19  2.50  0.54    5
-#> 198   8     1  27   2    5.5   66.0  112      2 -6.59 -5.92 -3.27    5
-#> 199   8     2  24   2    6.4   75.0  138      2 -3.32 -4.72 -4.10    6
-#> 201   8     4  24   1    7.1   70.5  122      2 -5.44 -4.47 -2.31    1
-#> 203   8     6  31   1    8.5   72.9  134      2 -5.71 -3.83 -0.79    1
-#> 205   8     8  36   2    9.4   78.0  146      2 -4.48 -3.18 -0.35    1
-#> 212   8    15  48   1   11.4  102.5  126      2 -0.20 -2.88 -4.22    2
-#> 254   9    30  42   1   17.9  109.4  164      2  2.41  1.23 -0.26    1
-#> 255  10     1  23   1    6.7   71.0  118      2 -5.32 -4.76 -3.23    5
-#> 274  11     1  24   2    5.8   71.9  108      2 -4.28 -5.34 -4.40    6
-#> 280  11     7  24   1    6.7   81.7  140      2 -1.77 -4.86 -5.63    6
-#> 283  11    10  36   1    8.5   78.3  126      2 -4.80 -4.20 -2.19    1
-#> 286  11    13  48   1    9.4   77.3  146      2 -6.21 -4.25 -0.69    1
-#> 290  11    17  24   2   12.4   99.9  136      2  4.40  0.62 -2.33    1
-#> 292  11    19  12   1   12.9   92.3  152      2  6.97  2.68 -0.50    5
-#> 301  11    28  24   2   15.1   85.3  140      2 -0.13  2.13  2.94    6
-#> 302  11    29  30   1   15.2   82.9  154      2 -2.65  1.13  3.76    2
-#> 303  11    30  48   2   15.8   90.5  132      2 -2.84 -0.12  2.29    2
-#> 307  12     3  36   1    7.5   90.0  130      2 -1.64 -4.99 -6.42    6
-#> 313  12     9  12   1   10.0   81.0  150      2  2.21  0.33 -0.75    1
-#> 315  12    11  48   1   10.6   84.0  142      2 -4.61 -3.43 -0.75    1
-#> 330  13     3  24   1    7.7   73.0  114      2 -4.62 -3.90 -2.06    1
-#> 340  13    13  12   2   11.1   79.0  152      2  1.94  1.72  1.26    5
-#> 345  13    18  24   1   13.3   96.1  142      2  2.94  0.79 -0.93    1
-#> 350  14     1  20   1    5.7   77.8  142      2 -2.27 -5.49 -6.47    6
-#> 352  14     3  48   1    6.5   80.7  140      2 -5.40 -6.22 -5.74    7
-#> 366  14    17  24   1   12.7   92.3  185      2  1.70  0.39 -0.70    1
-#> 368  14    19  48   1   13.4   66.3  144      2 -8.83 -1.58  7.33    3
-#> 379  15     1  12   1    5.1   66.0  106      2 -4.10 -5.24 -4.75    6
-#> 395  15    17  24   1   13.1   80.0  144      2 -2.33  0.66  2.62    2
-#> 399  15    21  36   1   14.3   66.0  154      2 -8.12 -0.02  8.58    3
-#> 400  15    22  48   1   14.5   68.0  152      2 -8.42 -0.95  7.80    3
-#> 403  16     2  24   1    7.0   74.0  130      2 -4.29 -4.57 -3.56    4
-#> 405  16     4  24   2    7.8   65.0  145      2 -6.42 -3.27  1.04    1
-#> 406  16     5  12   1    7.8   98.0  138      2  9.36 -1.93 -7.23    3
-#> 408  16     7  48   1    8.0   77.0  128      2 -6.28 -5.20 -2.66    5
-#> 432  17     3   6   1    7.9   98.4  138      2 14.38 -0.04 -7.18    3
-#> 433  17     4  48   2    8.3   94.9  136      2 -1.82 -4.79 -5.63    6
-#> 435  17     6   9   1    8.8   77.7  136      2  2.55 -0.11 -1.61    1
-#> 448  17    19  36   1   13.9  105.0  138      2  2.41 -0.26 -2.34    1
-#> 449  17    20  36   2   14.4  107.5  162      2  3.27  0.30 -2.27    1
-#> 460  17    31  48   1   18.5   96.2  170      2 -1.70  0.96  3.00    2
-#> 462  18     2   7   1    7.6   76.5  146      2  3.38 -0.80 -3.19    1
-#> 464  18     4  23   1    8.0   73.4  134      2 -4.52 -3.49 -1.69    1
-#> 468  18     8  36   1    9.3   77.6  140      2 -4.99 -3.57 -0.89    1
-#> 483  18    23  24   1   15.8  102.5  146      2  5.04  2.29 -0.21    5
-#> 489  18    29  48   2   19.2  109.9  164      2  1.66  1.24  0.35    1
-#> 490  19     1  12   2    5.3   72.0  152      2 -0.78 -4.27 -5.30    2
-#> 499  19    10  48   1   10.0   84.2  140      2 -4.56 -3.84 -1.53    1
-#> 508  19    19  24   1   13.7   98.0  180      2  3.56  1.05 -0.97    1
-#> 510  19    21  24   1   13.9   92.7  152      2  1.83  1.18  0.35    1
-#> 512  19    23  36   2   15.8  101.5  174      2  1.69  1.00  0.09    1
-#> 519  20     7  18   1    9.4   69.5  140      2 -4.73 -1.36  1.47    1
-#> 528  20    16  24   2   12.5   91.5  146      2  1.79  0.68 -0.46    1
-#> 530  20    18  24   2   13.2   91.2  160      2  1.70  1.11  0.22    1
-#> 536  20    24  48   2   17.5  109.9  154      2  1.66  0.61 -0.63    1
-#> 537  20    25  36   1   18.1  109.3  162      2  3.57  1.90 -0.11    5
-#> 557  21    19  24   2   11.4   92.0  138      2  1.95 -0.05 -1.64    1
-#> 587  22    20  36   2   12.7   80.4  154      2 -3.85 -0.68  2.38    2
-#> 591  22    24  36   1   14.0   69.0  152      2 -7.31 -0.20  6.77    3
-#> 594  23     1  36   1    5.4   80.0  140      2 -4.34 -6.66 -7.27    6
-#> 595  23     2  36   1    5.9   72.0  114      2 -6.50 -6.26 -4.96    7
-#> 596  23     3  24   1    6.3   77.0  130      2 -3.31 -5.24 -5.38    6
-#> 598  23     5  24   2    6.5   71.0  124      2 -4.56 -4.61 -2.93    5
-#> 599  23     6  36   1    6.5   80.0  130      2 -4.34 -5.79 -5.61    6
-#> 600  23     7  24   2    7.0   70.0  112      2 -4.87 -4.10 -1.75    1
-#> 604  23    11  14   1    8.0   66.0  136      2 -4.86 -2.11  0.77    1
-#> 607  23    14  36   1    8.3   74.0  138      2 -5.96 -4.36 -1.40    1
-#> 612  23    19  48   1   11.5   80.0  144      2 -5.56 -2.81  1.14    1
-#> 616  23    23  36   1   16.0   74.0  144      2 -5.96  0.90  6.82    3
-#> 621  24     5  24   1    8.4   72.2  140      2 -4.88 -3.22 -0.73    1
-#> 633  24    17  24   2   12.9   93.2  152      2  2.32  0.93 -0.46    1
-#> 640  25     1  12   2    6.3   99.3  110      2  9.82 -2.96 -8.25    3
-#> 641  25     2  48   2    6.7   85.0  140      2 -4.12 -5.90 -5.83    6
-#> 649  25    10  36   2    8.6   78.0  134      2 -4.48 -3.85 -1.38    1
-#> 661  25    22  24   2   12.4   91.0  140      2  1.64  0.62 -0.44    1
-#> 671  26     1  48   1    5.3   95.0  135      2 -1.99 -7.03 -9.71    6
-#> 672  26     2  18   2    5.6   67.0  108      2 -4.72 -4.84 -3.41    5
-#> 674  26     4  36   1    8.0   76.0  134      2 -5.42 -4.60 -2.40    5
-#> 679  26     9  48   1   10.5   82.0  142      2 -5.09 -3.50 -0.38    1
-#> 683  26    13  24   1   13.8   75.0  156      2 -3.97  1.11  4.39    2
-#> 685  26    15  24   1   14.3   85.0  168      2 -0.69  1.42  2.40    2
-#> 689  26    19  36   1   15.8  104.0  148      2  2.14  0.80 -0.54    1
-#> 690  26    20  36   1   16.0   79.0  162      2 -4.61  0.90  5.34    3
-#> 692  27     2  24   2    7.1   68.2  124      2 -5.43 -4.00 -1.04    1
-#> 698  27     8  36   2    8.4   75.4  124      2 -5.16 -4.01 -1.06    1
-#> 715  28     4  36   2    7.7  103.0  114      2  2.09 -4.60 -7.31    7
-#> 721  28    10  48   1   10.3   82.0  148      2 -5.09 -3.64 -0.61    1
-#> 723  28    12  15   1   11.0   73.0  162      2 -2.43  0.59  2.24    2
-#> 733  29     1  16   1    5.9   69.2  112      2 -4.26 -4.85 -4.17    6
-#> 734  29     2  17   1    6.1   69.3  114      2 -4.53 -4.75 -3.81    5
-#> 745  29    13  24   1   11.0   70.3  114      2 -5.50 -0.87  3.01    3
-#> 757  30     1  24   1    5.5   68.6  106      2 -6.06 -6.01 -4.76    7
-#> 767  30    11  36   2   10.2   77.5  142      2 -4.61 -2.49  0.66    1
-#> 781  30    25  24   2   13.3   91.5  152      2  1.79  1.16  0.24    1
-#> 783  30    27  36   1   14.2  102.3  138      2  1.68 -0.08 -1.48    1
-#> 784  30    28  36   1   14.6  106.1  154      2  2.70  0.15 -1.97    1
-#> 786  30    30  36   2   15.5  101.2  154      2  1.61  0.86 -0.05    1
-

The listed records can be checked and edited (see previous table). Anthropometric indices can then be recalculated and the flagging process repeated until all records that can be fixed have been fixed.

-

When listing records or displaying very large tables you may see a message like this:

-
#> [1] "[ reached getOption(\"max.print\") -- omitted 43 rows ]"
-

The max.print option sets a limit on the length of information that can be displayed by a single command. You can alter this behaviour using:

-
-options(max.print = 99999)
+
+svy[svy$flag != 0, ]
+#>     psu child age sex weight height muac oedema   haz   waz   whz flag
+#> 1     1     1  20   2    6.1   82.5  127      2 -0.07 -4.54 -6.03    2
+#> 3     1     3  15   1    7.1   67.5  124      2 -4.60 -3.34 -1.25    1
+#> 15    1    15  36   1   12.3   79.7  144      2 -4.42 -1.27  1.97    3
+#> 28    1    28  48   2   15.8  109.7  146      2  1.62 -0.12 -1.72    1
+#> 29    1    29  24   2   16.3  107.3  155      2  6.69  2.69 -0.82    5
+#> 31    1    31  48   2   18.8  109.9  166      2  1.66  1.10  0.13    1
+#> 32    2     1  12   1    6.1   99.4  112      2  9.95 -4.02 -9.18    3
+#> 34    2     3  24   2    6.5   76.0  108      2 -3.01 -4.61 -4.16    6
+#> 35    2     4  24   2    6.8   65.5  128      2 -6.27 -4.30 -0.63    1
+#> 36    2     5  36   1    7.3   76.0  110      2 -5.42 -5.15 -3.56    5
+#> 42    2    11  12   2    9.9   80.0  150      2  2.32  0.82 -0.21    1
+#> 44    2    13  36   2   10.5   78.0  142      2 -4.48 -2.24  0.87    1
+#> 52    2    21  36   1   12.7   77.5  144      2 -5.01 -1.01  2.77    3
+#> 57    2    26  24   1   15.5   93.7  166      2  2.16  2.13  1.46    5
+#> 59    3     1  18   2    5.7   67.0  110      2 -4.72 -4.72 -3.21    5
+#> 66    3     8  48   2    9.4   79.0  144      2 -5.51 -4.03 -0.57    1
+#> 76    3    18  24   2   12.1   96.0  138      2  3.19  0.42 -1.79    1
+#> 88    3    30  24   2   16.9  107.5  158      2  6.75  2.95 -0.47    5
+#> 89    4     1  26   2    6.6   71.7  114      2 -4.73 -4.74 -2.95    5
+#> 106   4    18  36   1   13.4   65.7  152      2 -8.20 -0.56  7.64    3
+#> 107   4    19  24   1   13.7   97.6  150      2  3.43  1.05 -0.89    1
+#> 122   5     4  24   1    8.0   73.3  130      2 -4.52 -3.61 -1.66    1
+#> 125   5     7  36   2   11.3  106.2  150      2  2.93 -1.63 -4.61    3
+#> 139   5    21  24   2   15.2   82.0  138      2 -1.15  2.18  3.97    6
+#> 154   6    14  24   2   11.9   91.0  148      2  1.64  0.29 -0.91    1
+#> 165   6    25  36   1   14.9  108.0  144      2  3.21  0.31 -2.13    1
+#> 173   7     2  10   2    6.5   76.2  122      2  1.91 -2.23 -4.20    3
+#> 174   7     3  36   2    6.8   66.6  134      2 -7.47 -5.35 -1.01    5
+#> 187   7    16  10   2   11.6   84.3  152      2  5.19  2.50  0.54    5
+#> 198   8     1  27   2    5.5   66.0  112      2 -6.59 -5.92 -3.27    5
+#> 199   8     2  24   2    6.4   75.0  138      2 -3.32 -4.72 -4.10    6
+#> 201   8     4  24   1    7.1   70.5  122      2 -5.44 -4.47 -2.31    1
+#> 203   8     6  31   1    8.5   72.9  134      2 -5.71 -3.83 -0.79    1
+#> 205   8     8  36   2    9.4   78.0  146      2 -4.48 -3.18 -0.35    1
+#> 212   8    15  48   1   11.4  102.5  126      2 -0.20 -2.88 -4.22    2
+#> 254   9    30  42   1   17.9  109.4  164      2  2.41  1.23 -0.26    1
+#> 255  10     1  23   1    6.7   71.0  118      2 -5.32 -4.76 -3.23    5
+#> 274  11     1  24   2    5.8   71.9  108      2 -4.28 -5.34 -4.40    6
+#> 280  11     7  24   1    6.7   81.7  140      2 -1.77 -4.86 -5.63    6
+#> 283  11    10  36   1    8.5   78.3  126      2 -4.80 -4.20 -2.19    1
+#> 286  11    13  48   1    9.4   77.3  146      2 -6.21 -4.25 -0.69    1
+#> 290  11    17  24   2   12.4   99.9  136      2  4.40  0.62 -2.33    1
+#> 292  11    19  12   1   12.9   92.3  152      2  6.97  2.68 -0.50    5
+#> 301  11    28  24   2   15.1   85.3  140      2 -0.13  2.13  2.94    6
+#> 302  11    29  30   1   15.2   82.9  154      2 -2.65  1.13  3.76    2
+#> 303  11    30  48   2   15.8   90.5  132      2 -2.84 -0.12  2.29    2
+#> 307  12     3  36   1    7.5   90.0  130      2 -1.64 -4.99 -6.42    6
+#> 313  12     9  12   1   10.0   81.0  150      2  2.21  0.33 -0.75    1
+#> 315  12    11  48   1   10.6   84.0  142      2 -4.61 -3.43 -0.75    1
+#> 330  13     3  24   1    7.7   73.0  114      2 -4.62 -3.90 -2.06    1
+#> 340  13    13  12   2   11.1   79.0  152      2  1.94  1.72  1.26    5
+#> 345  13    18  24   1   13.3   96.1  142      2  2.94  0.79 -0.93    1
+#> 350  14     1  20   1    5.7   77.8  142      2 -2.27 -5.49 -6.47    6
+#> 352  14     3  48   1    6.5   80.7  140      2 -5.40 -6.22 -5.74    7
+#> 366  14    17  24   1   12.7   92.3  185      2  1.70  0.39 -0.70    1
+#> 368  14    19  48   1   13.4   66.3  144      2 -8.83 -1.58  7.33    3
+#> 379  15     1  12   1    5.1   66.0  106      2 -4.10 -5.24 -4.75    6
+#> 395  15    17  24   1   13.1   80.0  144      2 -2.33  0.66  2.62    2
+#> 399  15    21  36   1   14.3   66.0  154      2 -8.12 -0.02  8.58    3
+#> 400  15    22  48   1   14.5   68.0  152      2 -8.42 -0.95  7.80    3
+#> 403  16     2  24   1    7.0   74.0  130      2 -4.29 -4.57 -3.56    4
+#> 405  16     4  24   2    7.8   65.0  145      2 -6.42 -3.27  1.04    1
+#> 406  16     5  12   1    7.8   98.0  138      2  9.36 -1.93 -7.23    3
+#> 408  16     7  48   1    8.0   77.0  128      2 -6.28 -5.20 -2.66    5
+#> 432  17     3   6   1    7.9   98.4  138      2 14.38 -0.04 -7.18    3
+#> 433  17     4  48   2    8.3   94.9  136      2 -1.82 -4.79 -5.63    6
+#> 435  17     6   9   1    8.8   77.7  136      2  2.55 -0.11 -1.61    1
+#> 448  17    19  36   1   13.9  105.0  138      2  2.41 -0.26 -2.34    1
+#> 449  17    20  36   2   14.4  107.5  162      2  3.27  0.30 -2.27    1
+#> 460  17    31  48   1   18.5   96.2  170      2 -1.70  0.96  3.00    2
+#> 462  18     2   7   1    7.6   76.5  146      2  3.38 -0.80 -3.19    1
+#> 464  18     4  23   1    8.0   73.4  134      2 -4.52 -3.49 -1.69    1
+#> 468  18     8  36   1    9.3   77.6  140      2 -4.99 -3.57 -0.89    1
+#> 483  18    23  24   1   15.8  102.5  146      2  5.04  2.29 -0.21    5
+#> 489  18    29  48   2   19.2  109.9  164      2  1.66  1.24  0.35    1
+#> 490  19     1  12   2    5.3   72.0  152      2 -0.78 -4.27 -5.30    2
+#> 499  19    10  48   1   10.0   84.2  140      2 -4.56 -3.84 -1.53    1
+#> 508  19    19  24   1   13.7   98.0  180      2  3.56  1.05 -0.97    1
+#> 510  19    21  24   1   13.9   92.7  152      2  1.83  1.18  0.35    1
+#> 512  19    23  36   2   15.8  101.5  174      2  1.69  1.00  0.09    1
+#> 519  20     7  18   1    9.4   69.5  140      2 -4.73 -1.36  1.47    1
+#> 528  20    16  24   2   12.5   91.5  146      2  1.79  0.68 -0.46    1
+#> 530  20    18  24   2   13.2   91.2  160      2  1.70  1.11  0.22    1
+#> 536  20    24  48   2   17.5  109.9  154      2  1.66  0.61 -0.63    1
+#> 537  20    25  36   1   18.1  109.3  162      2  3.57  1.90 -0.11    5
+#> 557  21    19  24   2   11.4   92.0  138      2  1.95 -0.05 -1.64    1
+#> 587  22    20  36   2   12.7   80.4  154      2 -3.85 -0.68  2.38    2
+#> 591  22    24  36   1   14.0   69.0  152      2 -7.31 -0.20  6.77    3
+#> 594  23     1  36   1    5.4   80.0  140      2 -4.34 -6.66 -7.27    6
+#> 595  23     2  36   1    5.9   72.0  114      2 -6.50 -6.26 -4.96    7
+#> 596  23     3  24   1    6.3   77.0  130      2 -3.31 -5.24 -5.38    6
+#> 598  23     5  24   2    6.5   71.0  124      2 -4.56 -4.61 -2.93    5
+#> 599  23     6  36   1    6.5   80.0  130      2 -4.34 -5.79 -5.61    6
+#> 600  23     7  24   2    7.0   70.0  112      2 -4.87 -4.10 -1.75    1
+#> 604  23    11  14   1    8.0   66.0  136      2 -4.86 -2.11  0.77    1
+#> 607  23    14  36   1    8.3   74.0  138      2 -5.96 -4.36 -1.40    1
+#> 612  23    19  48   1   11.5   80.0  144      2 -5.56 -2.81  1.14    1
+#> 616  23    23  36   1   16.0   74.0  144      2 -5.96  0.90  6.82    3
+#> 621  24     5  24   1    8.4   72.2  140      2 -4.88 -3.22 -0.73    1
+#> 633  24    17  24   2   12.9   93.2  152      2  2.32  0.93 -0.46    1
+#> 640  25     1  12   2    6.3   99.3  110      2  9.82 -2.96 -8.25    3
+#> 641  25     2  48   2    6.7   85.0  140      2 -4.12 -5.90 -5.83    6
+#> 649  25    10  36   2    8.6   78.0  134      2 -4.48 -3.85 -1.38    1
+#> 661  25    22  24   2   12.4   91.0  140      2  1.64  0.62 -0.44    1
+#> 671  26     1  48   1    5.3   95.0  135      2 -1.99 -7.03 -9.71    6
+#> 672  26     2  18   2    5.6   67.0  108      2 -4.72 -4.84 -3.41    5
+#> 674  26     4  36   1    8.0   76.0  134      2 -5.42 -4.60 -2.40    5
+#> 679  26     9  48   1   10.5   82.0  142      2 -5.09 -3.50 -0.38    1
+#> 683  26    13  24   1   13.8   75.0  156      2 -3.97  1.11  4.39    2
+#> 685  26    15  24   1   14.3   85.0  168      2 -0.69  1.42  2.40    2
+#> 689  26    19  36   1   15.8  104.0  148      2  2.14  0.80 -0.54    1
+#> 690  26    20  36   1   16.0   79.0  162      2 -4.61  0.90  5.34    3
+#> 692  27     2  24   2    7.1   68.2  124      2 -5.43 -4.00 -1.04    1
+#> 698  27     8  36   2    8.4   75.4  124      2 -5.16 -4.01 -1.06    1
+#> 715  28     4  36   2    7.7  103.0  114      2  2.09 -4.60 -7.31    7
+#> 721  28    10  48   1   10.3   82.0  148      2 -5.09 -3.64 -0.61    1
+#> 723  28    12  15   1   11.0   73.0  162      2 -2.43  0.59  2.24    2
+#> 733  29     1  16   1    5.9   69.2  112      2 -4.26 -4.85 -4.17    6
+#> 734  29     2  17   1    6.1   69.3  114      2 -4.53 -4.75 -3.81    5
+#> 745  29    13  24   1   11.0   70.3  114      2 -5.50 -0.87  3.01    3
+#> 757  30     1  24   1    5.5   68.6  106      2 -6.06 -6.01 -4.76    7
+#> 767  30    11  36   2   10.2   77.5  142      2 -4.61 -2.49  0.66    1
+#> 781  30    25  24   2   13.3   91.5  152      2  1.79  1.16  0.24    1
+#> 783  30    27  36   1   14.2  102.3  138      2  1.68 -0.08 -1.48    1
+#> 784  30    28  36   1   14.6  106.1  154      2  2.70  0.15 -1.97    1
+#> 786  30    30  36   2   15.5  101.2  154      2  1.61  0.86 -0.05    1
+

The listed records can be checked and edited (see previous table). +Anthropometric indices can then be recalculated and the flagging process +repeated until all records that can be fixed have been fixed.

+

When listing records or displaying very large tables you may see a +message like this:

+
#> [1] "[ reached getOption(\"max.print\") -- omitted 43 rows ]"
+

The max.print option sets a limit on the length of +information that can be displayed by a single command. You can alter +this behaviour using:

+
+options(max.print = 99999)
-
-

-Flagging data from older children

-

The process of flagging anthropometric indices in older children is very similar to that used with younger children.

+
+

Flagging data from older children +

+

The process of flagging anthropometric indices in older children is +very similar to that used with younger children.

We will retrieve a survey dataset:

-
-svy <- read.table("flag.ex02.csv", header = TRUE, sep = ",") 
-
#>   school sex ageMonths weight height   haz   baz
-#> 1   1112   1       173   25.5  179.0  1.70 -8.19
-#> 2   1113   2       145   22.7  164.0  1.79 -6.81
-#> 3   1116   1       150   13.5  135.0 -2.40 -8.64
-#> 4   1123   1       150   25.3  165.0  1.73 -6.92
-#> 5   1404   2       163   19.0  116.5 -6.05 -2.89
-#> 6   1501   2       185   27.4  136.6 -3.73 -2.85
-

The file flag.ex02.csv is a comma-separated-value (CSV) file containing anthropometric data from a survey of children aged 11 year or older and attending school in Ethiopia.

-

The variables of interest are height-for-age z-score (haz) and BMI-for-age z-score (baz). We will apply the WHO flagging criteria (see previous table) to these variables:

-
-svy$flag <- 0
-
-svy$flag <- ifelse(!is.na(svy$haz) & (svy$haz < -6 | svy$haz > 6), 
-              svy$flag + 1, svy$flag)
-
-svy$flag <- ifelse(!is.na(svy$baz) & (svy$baz < -5 | svy$baz > 5), 
-              svy$flag + 2, svy$flag)
-

Note that we do not usually apply SMART flagging criteria to older (i.e. > 59 months) children.

+
+svy <- read.table("flag.ex02.csv", header = TRUE, sep = ",") 
+
#>   school sex ageMonths weight height   haz   baz
+#> 1   1112   1       173   25.5  179.0  1.70 -8.19
+#> 2   1113   2       145   22.7  164.0  1.79 -6.81
+#> 3   1116   1       150   13.5  135.0 -2.40 -8.64
+#> 4   1123   1       150   25.3  165.0  1.73 -6.92
+#> 5   1404   2       163   19.0  116.5 -6.05 -2.89
+#> 6   1501   2       185   27.4  136.6 -3.73 -2.85
+

The file flag.ex02.csv is a comma-separated-value +(CSV) file containing anthropometric data from a survey of children aged +11 year or older and attending school in Ethiopia.

+

The variables of interest are height-for-age z-score +(haz) and BMI-for-age z-score (baz). +We will apply the WHO flagging criteria (see previous table) to these +variables:

+
+svy$flag <- 0
+
+svy$flag <- ifelse(!is.na(svy$haz) & (svy$haz < -6 | svy$haz > 6), 
+              svy$flag + 1, svy$flag)
+
+svy$flag <- ifelse(!is.na(svy$baz) & (svy$baz < -5 | svy$baz > 5), 
+              svy$flag + 2, svy$flag)
+

Note that we do not usually apply SMART flagging criteria to older +(i.e. > 59 months) children.

The coding of the flag variable is shown in previous table.

- +
@@ -1059,68 +1181,70 @@

Flagging codes based on powers of two and their meanings

This:

-
-table(svy$flag)
+
+table(svy$flag)

returns:

-
#> 
-#>   0   1   2 
-#> 960   2  11
-

This table shows the relative frequency of detected problems. See previous table to find the meaning of each of the codes. The number of flagged records can be found using:

-
-table(svy$flag != 0)["TRUE"]
+
#> 
+#>   0   1   2 
+#> 960   2  11
+

This table shows the relative frequency of detected problems. See +previous table to find the meaning of each of the codes. The number of +flagged records can be found using:

+
+table(svy$flag != 0)["TRUE"]

which returns:

-
#> TRUE 
-#>   13
+
#> TRUE 
+#>   13

The proportion of records that are flagged can be found using:

-
-prop.table(table(svy$flag != 0))["TRUE"]
+
+prop.table(table(svy$flag != 0))["TRUE"]

which returns:

-
#>       TRUE 
-#> 0.01336074
-

About 1.3% of records are flagged. This is an acceptably low proportion of records flagged. We can list flagged records using:

-
-svy[svy$flag != 0, ]
-#>     school sex ageMonths weight height   haz   baz flag
-#> 1     1112   1       173   25.5  179.0  1.70 -8.19    2
-#> 2     1113   2       145   22.7  164.0  1.79 -6.81    2
-#> 3     1116   1       150   13.5  135.0 -2.40 -8.64    2
-#> 4     1123   1       150   25.3  165.0  1.73 -6.92    2
-#> 5     1404   2       163   19.0  116.5 -6.05 -2.89    1
-#> 23    1501   2       137   24.7  155.0  1.09 -5.20    2
-#> 190   1507   1       173   24.0  154.0 -1.52 -6.46    2
-#> 328   1511   1       138   26.9  165.5  2.82 -6.29    2
-#> 969   1705   1       185   27.4  150.4 -2.62 -5.06    2
-#> 970   1708   1       197   23.9  126.2 -6.19 -3.17    1
-#> 971   1708   1       185   23.6  140.7 -3.86 -5.21    2
-#> 972   1909   2       174   26.5  153.7 -1.04 -5.04    2
-#> 973   2001   1       139   20.7  143.1 -0.49 -6.02    2
-

The listed records can be checked and edited (see previous table). Anthropometric indices can then be recalculated and the flagging process repeated until all records that can be fixed have been fixed.

+
#>       TRUE 
+#> 0.01336074
+

About 1.3% of records are flagged. This is an acceptably low +proportion of records flagged. We can list flagged records using:

+
+svy[svy$flag != 0, ]
+#>     school sex ageMonths weight height   haz   baz flag
+#> 1     1112   1       173   25.5  179.0  1.70 -8.19    2
+#> 2     1113   2       145   22.7  164.0  1.79 -6.81    2
+#> 3     1116   1       150   13.5  135.0 -2.40 -8.64    2
+#> 4     1123   1       150   25.3  165.0  1.73 -6.92    2
+#> 5     1404   2       163   19.0  116.5 -6.05 -2.89    1
+#> 23    1501   2       137   24.7  155.0  1.09 -5.20    2
+#> 190   1507   1       173   24.0  154.0 -1.52 -6.46    2
+#> 328   1511   1       138   26.9  165.5  2.82 -6.29    2
+#> 969   1705   1       185   27.4  150.4 -2.62 -5.06    2
+#> 970   1708   1       197   23.9  126.2 -6.19 -3.17    1
+#> 971   1708   1       185   23.6  140.7 -3.86 -5.21    2
+#> 972   1909   2       174   26.5  153.7 -1.04 -5.04    2
+#> 973   2001   1       139   20.7  143.1 -0.49 -6.02    2
+

The listed records can be checked and edited (see previous table). +Anthropometric indices can then be recalculated and the flagging process +repeated until all records that can be fixed have been fixed.

-
- - - + -
+ diff --git a/docs/articles/index.html b/docs/articles/index.html index 7f6e616..1781f77 100644 --- a/docs/articles/index.html +++ b/docs/articles/index.html @@ -1,229 +1,115 @@ - - - - - - - -Articles • nipnTK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Articles • nipnTK + Skip to contents + +
+ + +
- -
-
-
+ + - - - + diff --git a/docs/articles/nipnTK.html b/docs/articles/nipnTK.html index 5e987c4..b3c68e5 100644 --- a/docs/articles/nipnTK.html +++ b/docs/articles/nipnTK.html @@ -4,7 +4,8 @@ - + + The NiPN data quality toolkit • nipnTK @@ -12,189 +13,177 @@ - - - + + + - - + + - -
-
-
-
- + diff --git a/docs/articles/rl.html b/docs/articles/rl.html index 41593b7..e3880f7 100644 --- a/docs/articles/rl.html +++ b/docs/articles/rl.html @@ -4,7 +4,8 @@ - + + Checking ranges and legal values • nipnTK @@ -12,396 +13,426 @@ - - - + + + - - + + - -
-
-
-
-

This returns:

-
#> 
-#> Univariate outliers : Lower fence = 98, Upper fence = 178
-#>     age sex weight height  muac oedema
-#> 33   24   1    9.8   74.5 180.0      2
-#> 93   12   2    6.7   67.0  96.0      1
-#> 126  16   2    9.0   74.6 999.0      2
-#> 135  18   2    8.5   74.5 999.0      2
-#> 194  24   M    7.0   75.0  95.0      2
-#> 227   8   M    6.2   66.0  11.1      2
-#> 253  35   2    7.6   75.6  97.0      2
-#> 381  24   1   10.8   82.8  12.4      2
-#> 501  36   2   15.5   93.4 185.0      2
-#> 594  21   2    9.8   76.5  13.2      2
-#> 714  59   2   18.9   98.5 180.0      2
-#> 752  48   2   15.6  102.2 999.0      2
-#> 756  59   1   19.4  101.1 180.0      2
-#> 873  59   1   20.6  109.4 179.0      2
+
#> 
+#> Univariate outliers : Lower fence = 98, Upper fence = 178
+#>     age sex weight height  muac oedema
+#> 33   24   1    9.8   74.5 180.0      2
+#> 93   12   2    6.7   67.0  96.0      1
+#> 126  16   2    9.0   74.6 999.0      2
+#> 135  18   2    8.5   74.5 999.0      2
+#> 194  24   M    7.0   75.0  95.0      2
+#> 227   8   M    6.2   66.0  11.1      2
+#> 253  35   2    7.6   75.6  97.0      2
+#> 381  24   1   10.8   82.8  12.4      2
+#> 501  36   2   15.5   93.4 185.0      2
+#> 594  21   2    9.8   76.5  13.2      2
+#> 714  59   2   18.9   98.5 180.0      2
+#> 752  48   2   15.6  102.2 999.0      2
+#> 756  59   1   19.4  101.1 180.0      2
+#> 873  59   1   20.6  109.4 179.0      2

We can count the number of outliers or use:

-table(outliersUV(svy$muac))
+table(outliersUV(svy$muac))

This returns:

-
#> 
-#> Univariate outliers : Lower fence = 98, Upper fence = 178
-#> 
-#> FALSE  TRUE 
-#>   892    14
+
#> 
+#> Univariate outliers : Lower fence = 98, Upper fence = 178
+#> 
+#> FALSE  TRUE 
+#>   892    14

We can express this as a proportion:

+prop.table(table(outliersUV(svy$muac)))

This returns:

-
#> 
-#> Univariate outliers : Lower fence = 98, Upper fence = 178
-#> 
-#>      FALSE       TRUE 
-#> 0.98454746 0.01545254
+
#> 
+#> Univariate outliers : Lower fence = 98, Upper fence = 178
+#> 
+#>      FALSE       TRUE 
+#> 0.98454746 0.01545254

You may find it easier to use percentages:

-prop.table(table(outliersUV(svy$muac))) * 100
+prop.table(table(outliersUV(svy$muac))) * 100

This returns:

-
#> 
-#> Univariate outliers : Lower fence = 98, Upper fence = 178
-#> 
-#>     FALSE      TRUE 
-#> 98.454746  1.545254
-

Some of the muac values identified as potential outliers are possible muac values:

-
#> 
-#> Univariate outliers : Lower fence = 98, Upper fence = 178
-#>     age sex weight height  muac oedema
-#> 33   24   1    9.8   74.5 180.0      2
-#> 93   12   2    6.7   67.0  96.0      1
-#> 126  16   2    9.0   74.6 999.0      2
-#> 135  18   2    8.5   74.5 999.0      2
-#> 194  24   M    7.0   75.0  95.0      2
-#> 227   8   M    6.2   66.0  11.1      2
-#> 253  35   2    7.6   75.6  97.0      2
-#> 381  24   1   10.8   82.8  12.4      2
-#> 501  36   2   15.5   93.4 185.0      2
-#> 594  21   2    9.8   76.5  13.2      2
-#> 714  59   2   18.9   98.5 180.0      2
-#> 752  48   2   15.6  102.2 999.0      2
-#> 756  59   1   19.4  101.1 180.0      2
-#> 873  59   1   20.6  109.4 179.0      2
-

The outliersUV() function provides a fence parameter which alters the threshold at which a data point is considered to be an outlier.

-

The default fence = 1.5 defines the inner fence (i.e 1.5 times the interquartile range below the lower quartile and above the upper quartile). This will identify mild and severe outliers.

-

The value fence = 3 defines the outer fence (i.e 3 times the interquartile range below the lower quartile and above the upper quartile). This will identify severe outliers only:

+
#> 
+#> Univariate outliers : Lower fence = 98, Upper fence = 178
+#> 
+#>     FALSE      TRUE 
+#> 98.454746  1.545254
+

Some of the muac values identified as potential +outliers are possible muac values:

+
#> 
+#> Univariate outliers : Lower fence = 98, Upper fence = 178
+#>     age sex weight height  muac oedema
+#> 33   24   1    9.8   74.5 180.0      2
+#> 93   12   2    6.7   67.0  96.0      1
+#> 126  16   2    9.0   74.6 999.0      2
+#> 135  18   2    8.5   74.5 999.0      2
+#> 194  24   M    7.0   75.0  95.0      2
+#> 227   8   M    6.2   66.0  11.1      2
+#> 253  35   2    7.6   75.6  97.0      2
+#> 381  24   1   10.8   82.8  12.4      2
+#> 501  36   2   15.5   93.4 185.0      2
+#> 594  21   2    9.8   76.5  13.2      2
+#> 714  59   2   18.9   98.5 180.0      2
+#> 752  48   2   15.6  102.2 999.0      2
+#> 756  59   1   19.4  101.1 180.0      2
+#> 873  59   1   20.6  109.4 179.0      2
+

The outliersUV() function provides a +fence parameter which alters the threshold at which a +data point is considered to be an outlier.

+

The default fence = 1.5 defines the inner +fence (i.e 1.5 times the interquartile range below +the lower quartile and above the upper quartile). This will identify +mild and severe outliers.

+

The value fence = 3 defines the outer fence +(i.e 3 times the interquartile range below the lower +quartile and above the upper quartile). This will identify +severe outliers only:

-svy[outliersUV(svy$muac, fence = 3), ]
+svy[outliersUV(svy$muac, fence = 3), ]

This returns:

-
#> 
-#> Univariate outliers : Lower fence = 68, Upper fence = 208
-#>     age sex weight height  muac oedema
-#> 126  16   2    9.0   74.6 999.0      2
-#> 135  18   2    8.5   74.5 999.0      2
-#> 227   8   M    6.2   66.0  11.1      2
-#> 381  24   1   10.8   82.8  12.4      2
-#> 594  21   2    9.8   76.5  13.2      2
-#> 752  48   2   15.6  102.2 999.0      2
-

There is something wrong with all of these values of muac.

-

The intention was that the muac variable records mid-upper-arm-circumference (MUAC) in mm. There are some impossibly small (i.e. 11.1, 12.4, and 13.2) and impossibly large values (i.e. 999.0).

-

The three impossibly small values are probably due to data being recorded in cm rather than mm. It is probably safe to change these three values to 111, 124 and 132. It is easiest to do this each record separately:

+
#> 
+#> Univariate outliers : Lower fence = 68, Upper fence = 208
+#>     age sex weight height  muac oedema
+#> 126  16   2    9.0   74.6 999.0      2
+#> 135  18   2    8.5   74.5 999.0      2
+#> 227   8   M    6.2   66.0  11.1      2
+#> 381  24   1   10.8   82.8  12.4      2
+#> 594  21   2    9.8   76.5  13.2      2
+#> 752  48   2   15.6  102.2 999.0      2
+

There is something wrong with all of these values of +muac.

+

The intention was that the muac variable records +mid-upper-arm-circumference (MUAC) in mm. There are some impossibly +small (i.e. 11.1, 12.4, and +13.2) and impossibly large values +(i.e. 999.0).

+

The three impossibly small values are probably due to data being +recorded in cm rather than mm. It is probably safe to change these three +values to 111, 124 and 132. It is easiest to do this each record +separately:

-svy$muac[svy$muac == 11.1] <- 111
-

An alternative approach is to specify row numbers instead of values:

+svy$muac[svy$muac == 11.1] <- 111
+

An alternative approach is to specify row numbers instead of +values:

-svy$muac[381] <- 124
-svy$muac[594] <- 132
-

The three 999.0 values are missing values coded as 999.0. It is safe to set these three values to missing using the special NA value:

+svy$muac[381] <- 124 +svy$muac[594] <- 132
+

The three 999.0 values are missing values coded as +999.0. It is safe to set these three values to missing using the special +NA value:

-svy$muac[svy$muac == 999.00] <- NA
-

Range checks should be repeated after editing the data to ensure that the problems have been fixed:

+svy$muac[svy$muac == 999.00] <- NA +

Range checks should be repeated after editing the data to ensure that +the problems have been fixed:

-summary(svy$muac)
-svy[outliersUV(svy$muac), ]
-svy[outliersUV(svy$muac, fence = 3), ]
-

Following is a boxplot of the muac variable made using:

+summary(svy$muac) +svy[outliersUV(svy$muac), ] +svy[outliersUV(svy$muac, fence = 3), ] +

Following is a boxplot of the muac variable made +using:

-boxplot(svy$muac, horizontal = TRUE, xlab = "MUAC (mm)", frame.plot = FALSE)
-

after the fixes for incorrectly entered data and missing values were made.

+boxplot(svy$muac, horizontal = TRUE, xlab = "MUAC (mm)", frame.plot = FALSE) +

after the fixes for incorrectly entered data and missing values were +made.

There should now be no severe outliers:

-prop.table(table(outliersUV(svy$muac, fence = 3))) * 100
+prop.table(table(outliersUV(svy$muac, fence = 3))) * 100

returns:

-
#> 
-#> Univariate outliers : Lower fence = 68, Upper fence = 208
-#> 
-#> FALSE 
-#>   100
-

It is usually better to identify and edit only the most extreme univariate outliers, as we have done here, and use the scatterplot and statistical distance methods described elsewhere in this toolkit to identify other potential outliers.

+
#> 
+#> Univariate outliers : Lower fence = 68, Upper fence = 208
+#> 
+#> FALSE 
+#>   100
+

It is usually better to identify and edit only the most extreme +univariate outliers, as we have done here, and use the +scatterplot and statistical distance methods described elsewhere in this +toolkit to identify other potential outliers.

-
-

-Editing data

-

We have edited records with outliers at the R command line.

-

It is a good idea to edit data at the command line or using a script containing the required commands.

+
+

Editing data +

+

We have edited records with outliers at the R command +line.

+

It is a good idea to edit data at the command line or using a script +containing the required commands.

A script provides a record of changes made to the data.

-

R also keeps a record of whatever you do at the command line in a “history file”. The history file is a plain text file which is usually called .Rhistory and stored in your home directory.

+

R also keeps a record of whatever you do at the command line +in a “history file”. The history file is a plain text file which is +usually called .Rhistory and stored in your home directory.

Some regulatory authorities require you to keep a history file.

-

Some publications may require you to provide a “reproducible data analysis”. This could be an edited and annotated copy of your history file.

-

The edit() function provides a basic tool for editing data interactively.

-

Editing data using the edit() function is typically a three stage process:

+

Some publications may require you to provide a “reproducible data +analysis”. This could be an edited and annotated copy of your history +file.

+

The edit() function provides a basic tool for editing +data interactively.

+

Editing data using the edit() function is typically a +three stage process:

    -
  1. Create a new object containing only the data that requires editing.

  2. -
  3. Use the edit() function to edit data in the new object closing the data editor window when you are finished.

  4. +
  5. Create a new object containing only the data that requires +editing.

  6. +
  7. Use the edit() function to edit data in the new +object closing the data editor window when you are finished.

  8. Replace the old records with the edited records.

We will try this using a separate copy of the example data:

-x <- rl.ex01
-records2update <- x[outliersUV(x$muac, fence = 3), ]
-records2update <- edit(records2update)
-x[row.names(records2update), ] <- records2update
+x <- rl.ex01 +records2update <- x[outliersUV(x$muac, fence = 3), ] +records2update <- edit(records2update) +x[row.names(records2update), ] <- records2update

We can check that the edits have been made using:

-
#> 
-#> Univariate outliers : Lower fence = 68, Upper fence = 208
+
#> 
+#> Univariate outliers : Lower fence = 68, Upper fence = 208
-x[outliersUV(x$muac, fence = 3), ]
+x[outliersUV(x$muac, fence = 3), ]

If you have fixed the problems in the data this should return:

-
#> 
-#> Univariate outliers : Lower fence = 68, Upper fence = 208
-#>     age sex weight height  muac oedema
-#> 126  16   2    9.0   74.6 999.0      2
-#> 135  18   2    8.5   74.5 999.0      2
-#> 227   8   M    6.2   66.0  11.1      2
-#> 381  24   1   10.8   82.8  12.4      2
-#> 594  21   2    9.8   76.5  13.2      2
-#> 752  48   2   15.6  102.2 999.0      2
-

The edit() function works differently on different operating systems and with different graphical user interfaces. If you are using RStudio or RAnalyticFlow on OS X you will need to install XQuartz if you want to use the edit() function. XQuartz is available from:

-

https://www.xquartz.org/index.html

+
#> 
+#> Univariate outliers : Lower fence = 68, Upper fence = 208
+#>     age sex weight height  muac oedema
+#> 126  16   2    9.0   74.6 999.0      2
+#> 135  18   2    8.5   74.5 999.0      2
+#> 227   8   M    6.2   66.0  11.1      2
+#> 381  24   1   10.8   82.8  12.4      2
+#> 594  21   2    9.8   76.5  13.2      2
+#> 752  48   2   15.6  102.2 999.0      2
+

The edit() function works differently on different +operating systems and with different graphical user interfaces. If you +are using RStudio or RAnalyticFlow on OS X you will +need to install XQuartz if you want to use the +edit() function. XQuartz is available from:

+

https://www.xquartz.org/index.html

-
-

-Checking categorical variables

-

We can use the table() function to examine the codes used in categorical variables. For example:

+
+

Checking categorical variables +

+

We can use the table() function to examine the codes +used in categorical variables. For example:

-table(svy$sex)
+table(svy$sex)

returns:

-
#> 
-#>   1   2   3   F   M 
-#> 404 458   1  24  19
-

The intention was that the sex variable was coded using 1 for male and 2 for female but in a small number of records the codes M for male and F for female have been used. A mixed coding scheme like this will complicate data-management and data-analysis. Data in the sex variable should be edited to ensure that consistent coding is used:

+
#> 
+#>   1   2   3   F   M 
+#> 404 458   1  24  19
+

The intention was that the sex variable was coded +using 1 for male and 2 for female but in a small number of records the +codes M for male and F for female have +been used. A mixed coding scheme like this will complicate +data-management and data-analysis. Data in the sex variable should be +edited to ensure that consistent coding is used:

-svy$sex[svy$sex == "M"] <- 1
-svy$sex[svy$sex == "F"] <- 2
-

You may find that a few records contain meaningless codes. The code 3 in the example dataset has, very probably, no meaning and is likely to be a simple data entry error. This record should be checked and corrected, if possible. If the record cannot be corrected then the sex variable should be set to missing:

+svy$sex[svy$sex == "M"] <- 1 +svy$sex[svy$sex == "F"] <- 2
+

You may find that a few records contain meaningless codes. The code +3 in the example dataset has, very probably, no meaning +and is likely to be a simple data entry error. This record should be +checked and corrected, if possible. If the record cannot be corrected +then the sex variable should be set to missing:

-svy$sex[svy$sex == 3] <- NA
-

Legal value checks should be repeated after editing to ensure that problems have been fixed:

+svy$sex[svy$sex == 3] <- NA +

Legal value checks should be repeated after editing to ensure that +problems have been fixed:

-table(svy$sex)
+table(svy$sex)

now returns:

-
#> 
-#>   1   2   3   F   M 
-#> 423 482   0   0   0
-

The table contains cells for the values M, F, and 3 because R imported the variable as a categorical or “factor” variable:

+
#> 
+#>   1   2   3   F   M 
+#> 423 482   0   0   0
+

The table contains cells for the values M, +F, and 3 because R imported +the variable as a categorical or “factor” variable:

-str(svy)
+str(svy)

returns:

-
#> 'data.frame':    906 obs. of  6 variables:
-#>  $ age   : int  12 6 6 8 12 8 18 9 12 12 ...
-#>  $ sex   : Factor w/ 5 levels "1","2","3","F",..: 2 1 2 1 1 1 1 1 2 1 ...
-#>  $ weight: num  6.7 6.4 6.5 7.2 6.1 7.7 6.4 7.8 7.5 6.5 ...
-#>  $ height: num  68.5 65 65.6 68.4 65.4 66.5 66.7 65.3 69.1 70.3 ...
-#>  $ muac  : num  148 125 125 144 114 146 119 140 138 121 ...
-#>  $ oedema: int  2 2 2 2 2 2 2 2 2 2 ...
+
#> 'data.frame':    906 obs. of  6 variables:
+#>  $ age   : int  12 6 6 8 12 8 18 9 12 12 ...
+#>  $ sex   : Factor w/ 5 levels "1","2","3","F",..: 2 1 2 1 1 1 1 1 2 1 ...
+#>  $ weight: num  6.7 6.4 6.5 7.2 6.1 7.7 6.4 7.8 7.5 6.5 ...
+#>  $ height: num  68.5 65 65.6 68.4 65.4 66.5 66.7 65.3 69.1 70.3 ...
+#>  $ muac  : num  148 125 125 144 114 146 119 140 138 121 ...
+#>  $ oedema: int  2 2 2 2 2 2 2 2 2 2 ...

We can fix this by redefining the levels of the sex variable:

-levels(svy$sex) <- c("1", "2", NA, NA, NA)
-table(svy$sex)
-#> 
-#>   1   2 
-#> 423 482
+levels(svy$sex) <- c("1", "2", NA, NA, NA) +table(svy$sex) +#> +#> 1 2 +#> 423 482 -
-

-Saving changes

+
+

Saving changes +

We have edited some data.

We usually want to save changes.

-

It is simple to save a dataset in a comma-separated-value (CSV) text file using the write.table() function:

+

It is simple to save a dataset in a comma-separated-value (CSV) text +file using the write.table() function:

-write.table(x = svy, file = "rl.ex01.clean.csv", sep = ",", quote = FALSE, 
-            row.names = FALSE, fileEncoding = "ASCII")
-

R can work with a variety of files format but it is usually simplest to work with simple text files.

+write.table(x = svy, file = "rl.ex01.clean.csv", sep = ",", quote = FALSE, + row.names = FALSE, fileEncoding = "ASCII")
+

R can work with a variety of files format but it is usually +simplest to work with simple text files.

- - - - + -
+ diff --git a/docs/articles/rl_files/figure-html/unnamed-chunk-24-1.png b/docs/articles/rl_files/figure-html/unnamed-chunk-24-1.png index 5c3bee8..aa1657f 100644 Binary files a/docs/articles/rl_files/figure-html/unnamed-chunk-24-1.png and b/docs/articles/rl_files/figure-html/unnamed-chunk-24-1.png differ diff --git a/docs/articles/rl_files/figure-html/unnamed-chunk-7-1.png b/docs/articles/rl_files/figure-html/unnamed-chunk-7-1.png index eb7f4fe..5b9a354 100644 Binary files a/docs/articles/rl_files/figure-html/unnamed-chunk-7-1.png and b/docs/articles/rl_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/docs/articles/sp.html b/docs/articles/sp.html index 55d8ee1..a0082c7 100644 --- a/docs/articles/sp.html +++ b/docs/articles/sp.html @@ -4,7 +4,8 @@ - + + Using scatterplots to identify outliers • nipnTK @@ -12,520 +13,616 @@ - - - + + + - - + + - -
-
-
-
- +

The pch = ifelse(svy$oedema == 1, 19, 1) tells the +plot() function to plot filled circles for oedema cases and +open circles for children without oedema. The resulting plot is shown +below.

-

A single high weight for height outlier appears to be due to the presence of oedema.

-

The other filled circles that are located in the main mass of plotted points show that children with oedema may have a body weight within the normal range for their height. These children may not be not wasted but they are suffering from a form of severe acute malnutrition (SAM) known as kwashiorkor.

-

Outliers can be identified by eye. The identify() function can help with this:

+

A single high weight for height outlier appears to be due to the +presence of oedema.

+

The other filled circles that are located in the main mass of plotted +points show that children with oedema may have a body weight within the +normal range for their height. These children may not be not wasted but +they are suffering from a form of severe acute malnutrition (SAM) known +as kwashiorkor.

+

Outliers can be identified by eye. The identify() +function can help with this:

-plot(svy$height, svy$weight, pch = ifelse(svy$oedema == 1, 19, 1))
-identify(svy$height, svy$weight)
-

Clicking on any point will cause the record (row) number associated with each point to be displayed on the plot (as shown below).

+plot(svy$height, svy$weight, pch = ifelse(svy$oedema == 1, 19, 1)) +identify(svy$height, svy$weight)
+

Clicking on any point will cause the record (row) number associated +with each point to be displayed on the plot (as shown below).

-

Right-clicking on the plot or pressing the “escape” key will stop identify().

-

The behaviour of the identify() function may be different when you use an alternative user interface for R such as RStudio or RAnalyticFlow.

-

The identify() function will, by default, display record (row) numbers for identified points. This is usually what is needed. Alternative labels can be displayed. For example:

+

Right-clicking on the plot or pressing the “escape” key will stop +identify().

+

The behaviour of the identify() function may be +different when you use an alternative user interface for R such as +RStudio or RAnalyticFlow.

+

The identify() function will, by default, display record +(row) numbers for identified points. This is usually what is needed. +Alternative labels can be displayed. For example:

-plot(svy$height, svy$weight, pch = ifelse(svy$oedema == 1, 19, 1)) 
-identify(svy$height, svy$weight, 
-         labels = paste(svy$height, svy$weight, sep = ";"), 
-         cex = 0.75)
+plot(svy$height, svy$weight, pch = ifelse(svy$oedema == 1, 19, 1)) +identify(svy$height, svy$weight, + labels = paste(svy$height, svy$weight, sep = ";"), + cex = 0.75)

displays the height and weight values at selected points.

-

The ability to display custom labels is useful if there is a variable (column) in a dataset that contains unique record identifiers.

-

It is useful to be able to store the record (row) numbers of identified points:

+

The ability to display custom labels is useful if there is a variable +(column) in a dataset that contains unique record identifiers.

+

It is useful to be able to store the record (row) numbers of +identified points:

-plot(svy$height, svy$weight, pch = ifelse(svy$oedema == 1, 19, 1)) 
-stored <- identify(svy$height, svy$weight)
-

If the same points shown in the previous figure are clicked to identify them then:

+plot(svy$height, svy$weight, pch = ifelse(svy$oedema == 1, 19, 1)) +stored <- identify(svy$height, svy$weight)
+

If the same points shown in the previous figure are clicked to +identify them then:

-stored
+stored

will return:

-
#> [1] "1"  "6"  "16" "62" "66"
+
#> [1] "1"  "6"  "16" "62" "66"

We can examine the data for the identified points:

-svy[stored, ]
+svy[stored, ]

This returns:

-
#>    age sex weight height muac oedema
-#> 1   54   1   20.5  111.5  180      2
-#> 6   48   2   18.6   95.3  171      1
-#> 16  30   1   16.9   92.5  188      2
-#> 62  55   1   15.1  118.0  156      2
-#> 66  56   1   15.0  115.0  148      2
-

The oedema data is coded 1 for present and 2 for absent.

-

Data can be checked and edited if needed. Note that record 6 is an oedema case and should probably be left alone.

-

If your dataset has many variables (columns) then you may specify only the variables (columns) of interest:

+
#>    age sex weight height muac oedema
+#> 1   54   1   20.5  111.5  180      2
+#> 6   48   2   18.6   95.3  171      1
+#> 16  30   1   16.9   92.5  188      2
+#> 62  55   1   15.1  118.0  156      2
+#> 66  56   1   15.0  115.0  148      2
+

The oedema data is coded 1 for +present and 2 for absent.

+

Data can be checked and edited if needed. Note that record +6 is an oedema case and should probably be left +alone.

+

If your dataset has many variables (columns) then you may specify +only the variables (columns) of interest:

-svy[stored, c("weight", "height", "oedema")]
+svy[stored, c("weight", "height", "oedema")]

This returns:

-
#>    weight height oedema
-#> 1    20.5  111.5      2
-#> 6    18.6   95.3      1
-#> 16   16.9   92.5      2
-#> 62   15.1  118.0      2
-#> 66   15.0  115.0      2
+
#>    weight height oedema
+#> 1    20.5  111.5      2
+#> 6    18.6   95.3      1
+#> 16   16.9   92.5      2
+#> 62   15.1  118.0      2
+#> 66   15.0  115.0      2
-
-

-Identifying outliers using statistical distance

-

A more formal method of identifying outliers is to use a measure of the statistical distance. A common measure of statistical distance that is applied to scatterplot data is the Mahalanobis distance. This treats the bivariate probability distribution as an ellipsoid. The Mahalanobis distance is the distance of a point from the centre of mass of the distribution divided by width of the ellipsoid in the direction of the point:

-

-

In directions in which the ellipsoid has a short axis the test point must be close to the centre of mass of the distribution. In directions in which the ellipsoid has a long axis the test point may be more distant from the centre of mass of the distribution.

-

The NiPN data quality toolkit provides an R language function outliersMD() that uses the Mahalanobis distance to identify outliers in the same dataset:

+
+

Identifying outliers using statistical distance +

+

A more formal method of identifying outliers is to use a measure of +the statistical distance. A common measure of statistical +distance that is applied to scatterplot data is the Mahalanobis +distance. This treats the bivariate probability distribution as an +ellipsoid. The Mahalanobis distance is the distance of a point from the +centre of mass of the distribution divided by width of the ellipsoid in +the direction of the point:

+

+

In directions in which the ellipsoid has a short axis the test point +must be close to the centre of mass of the distribution. In directions +in which the ellipsoid has a long axis the test point may be more +distant from the centre of mass of the distribution.

+

The NiPN data quality toolkit provides an R language +function outliersMD() that uses the Mahalanobis distance to +identify outliers in the same dataset:

-svy[outliersMD(svy$height, svy$weight), ]
+svy[outliersMD(svy$height, svy$weight), ]

This returns the same set of records that was identified by eye:

-
#>    age sex weight height muac oedema
-#> 1   54   1   20.5  111.5  180      2
-#> 6   48   2   18.6   95.3  171      1
-#> 16  30   1   16.9   92.5  188      2
-#> 62  55   1   15.1  118.0  156      2
-#> 66  56   1   15.0  115.0  148      2
-

Data can be checked and edited if needed. Note that record 6 is an oedema case and should probably be left alone.

-

We can use the outliersMD() to identify and display outliers on a scatterplot:

+
#>    age sex weight height muac oedema
+#> 1   54   1   20.5  111.5  180      2
+#> 6   48   2   18.6   95.3  171      1
+#> 16  30   1   16.9   92.5  188      2
+#> 62  55   1   15.1  118.0  156      2
+#> 66  56   1   15.0  115.0  148      2
+

Data can be checked and edited if needed. Note that record +6 is an oedema case and should probably be left +alone.

+

We can use the outliersMD() to identify and display +outliers on a scatterplot:

-plot(svy$height, svy$weight,
-pch = ifelse(outliersMD(svy$height, svy$weight), 19, 1))
+plot(svy$height, svy$weight, +pch = ifelse(outliersMD(svy$height, svy$weight), 19, 1))

-

The outliersMD() function has an alpha parameter. The default value for the alpha parameter is alpha = 0.001. This value is used automatically unless another value is specified.

-

When we use alpha = 0.001 we are looking for records with values so extreme that we would expect to find them with a probability of 0.001 when there are no problems with the data.

-

We can calculate the number of outliers that we expect to see by chance with alpha = 0.001 using:

+

The outliersMD() function has an alpha +parameter. The default value for the alpha parameter is +alpha = 0.001. This value is used automatically unless +another value is specified.

+

When we use alpha = 0.001 we are looking for records +with values so extreme that we would expect to find them with a +probability of 0.001 when there are no problems with the data.

+

We can calculate the number of outliers that we expect to see by +chance with alpha = 0.001 using:

-round(nrow(svy) * 0.001)
+round(nrow(svy) * 0.001)

This returns:

-
#> [1] 1
-

We found five potential outliers. The difference between the number that we expected and the number that we observed (i.e. one expected vs. five observed) suggests that some of the identified outliers are true outliers or due to data errors.

-

Another way of looking at the alpha parameter is that it alters the sensitivity of the outlierMD() function for detecting outliers by altering the threshold distance that is used to define outliers. This can be useful when using the outlierMD() function with some, but not all, curvilinear relationships (see below).

-

Larger values of alpha will tend to detect more potential outliers. For example:

+
#> [1] 1
+

We found five potential outliers. The difference between the number +that we expected and the number that we observed (i.e. one expected +vs. five observed) suggests that some of the identified outliers are +true outliers or due to data errors.

+

Another way of looking at the alpha parameter is +that it alters the sensitivity of the outlierMD() function +for detecting outliers by altering the threshold distance that is used +to define outliers. This can be useful when using the +outlierMD() function with some, but not all, curvilinear +relationships (see below).

+

Larger values of alpha will tend to detect more +potential outliers. For example:

-plot(svy$height, svy$weight,
-     pch = ifelse(outliersMD(svy$height, svy$weight, alpha = 0.01), 19, 1))
+plot(svy$height, svy$weight, + pch = ifelse(outliersMD(svy$height, svy$weight, alpha = 0.01), 19, 1))

and:

-svy[outliersMD(svy$height,svy$weight, alpha = 0.01), ]
-#>     age sex weight height muac oedema
-#> 1    54   1   20.5  111.5  180      2
-#> 2    53   1   19.3  108.0  167      2
-#> 3    51   2   19.3  106.0  163      2
-#> 4    44   1   18.9  111.0  163      2
-#> 5    47   1   18.8  103.0  173      2
-#> 6    48   2   18.6   95.3  171      1
-#> 16   30   1   16.9   92.5  188      2
-#> 32   43   1   16.2   92.6  166      2
-#> 61   26   1   15.1   87.6  168      2
-#> 62   55   1   15.1  118.0  156      2
-#> 66   56   1   15.0  115.0  148      2
-#> 477  38   2   10.3   94.6  160      2
-#> 487  32   2   10.2   93.0  150      2
-#> 722  17   2    8.6   63.3  136      2
-

In almost all cases the default alpha = 0.001 will be appropriate.

-

The techniques outlined above can be used to examine the relationships between other pairs of anthropometric variables (e.g. weight and muac) and to identify outliers. All sensible pairings of variables should be examined.

+svy[outliersMD(svy$height,svy$weight, alpha = 0.01), ] +#> age sex weight height muac oedema +#> 1 54 1 20.5 111.5 180 2 +#> 2 53 1 19.3 108.0 167 2 +#> 3 51 2 19.3 106.0 163 2 +#> 4 44 1 18.9 111.0 163 2 +#> 5 47 1 18.8 103.0 173 2 +#> 6 48 2 18.6 95.3 171 1 +#> 16 30 1 16.9 92.5 188 2 +#> 32 43 1 16.2 92.6 166 2 +#> 61 26 1 15.1 87.6 168 2 +#> 62 55 1 15.1 118.0 156 2 +#> 66 56 1 15.0 115.0 148 2 +#> 477 38 2 10.3 94.6 160 2 +#> 487 32 2 10.2 93.0 150 2 +#> 722 17 2 8.6 63.3 136 2 +

In almost all cases the default alpha = 0.001 will +be appropriate.

+

The techniques outlined above can be used to examine the +relationships between other pairs of anthropometric variables +(e.g. weight and muac) and to identify +outliers. All sensible pairings of variables should be examined.

-
-

-Anthropometric measurements and age

-

We also expect anthropometric variables to be associated with age. This relationship is particularly strong in children. It will be less strong in adults and may be weak or even reversed in older people.

-

We can explore the relationship between an anthropometric variable and age using the techniques described above. For example:

+
+

Anthropometric measurements and age +

+

We also expect anthropometric variables to be associated with age. +This relationship is particularly strong in children. It will be less +strong in adults and may be weak or even reversed in older people.

+

We can explore the relationship between an anthropometric variable +and age using the techniques described above. For example:

-plot(svy$age, svy$height, pch = ifelse(outliersMD(svy$age, svy$height), 19, 1)) 
-svy[outliersMD(svy$age, svy$height), ]
+plot(svy$age, svy$height, pch = ifelse(outliersMD(svy$age, svy$height), 19, 1)) +svy[outliersMD(svy$age, svy$height), ]

-
#>    age sex weight height muac oedema
-#> 4   44   1   18.9    111  163      2
-#> 62  55   1   15.1    118  156      2
-

There are some problems with this approach. Age is often reported and recorded with considerable age heaping. Age is unlikely to be approximately normally distributed, which is an assumption of the Mahalanobis distance method. The relationship between anthropometric variables and age usually follows a “growth curve” rather than a straight line.

-

The combination of age heaping, non-normality, and a curvilinear relationship may reduce the effectiveness of the Mahalanobis distance method for detecting outliers. It may be useful, in such cases, to increase the value of the alpha parameter. For example:

+
#>    age sex weight height muac oedema
+#> 4   44   1   18.9    111  163      2
+#> 62  55   1   15.1    118  156      2
+

There are some problems with this approach. Age is often reported and +recorded with considerable age heaping. Age is unlikely to be +approximately normally distributed, which is an assumption of the +Mahalanobis distance method. The relationship between anthropometric +variables and age usually follows a “growth curve” rather than a +straight line.

+

The combination of age heaping, non-normality, and a curvilinear +relationship may reduce the effectiveness of the Mahalanobis distance +method for detecting outliers. It may be useful, in such cases, to +increase the value of the alpha parameter. For +example:

-plot(svy$age, svy$height, pch = ifelse(outliersMD(svy$age, svy$height, alpha = 0.025), 19, 1))
+plot(svy$age, svy$height, pch = ifelse(outliersMD(svy$age, svy$height, alpha = 0.025), 19, 1))

-

Outliers can be listed using the same value for alpha:

+

Outliers can be listed using the same value for +alpha:

-svy[outliersMD(svy$age, svy$height, alpha = 0.025), ]
-#>     age sex weight height muac oedema
-#> 1    54   1   20.5  111.5  180      2
-#> 4    44   1   18.9  111.0  163      2
-#> 7    55   1   18.6  109.3  156      2
-#> 14   48   1   17.0  109.0  175      2
-#> 27   56   2   16.4  110.0  149      2
-#> 62   55   1   15.1  118.0  156      2
-#> 66   56   1   15.0  115.0  148      2
-#> 113  58   1   14.2   92.0  148      2
-#> 129  23   2   14.0   95.0  161      2
-#> 190  15   2   13.0   90.5  150      2
-#> 212  21   2   12.8   93.0  152      2
-#> 378  51   2   11.2   83.0  141      2
-#> 453  49   1   10.6   83.0  139      2
-#> 461  54   2   10.5   86.8  132      2
-#> 551  41   2    9.8   78.0  139      2
-#> 599  50   1    9.5   84.7  123      2
-#> 660  49   1    9.1   79.5  129      2
-#> 722  17   2    8.6   63.3  136      2
-#> 809  41   2    7.9   75.7  120      2
-#> 881  30   1    6.5   69.6  103      2
-#> 893  18   2    5.8   63.2  106      2
-

The Mahalanobis distance method is usually robust enough to deal with age data provided an appropriate value for alpha is used.

+svy[outliersMD(svy$age, svy$height, alpha = 0.025), ] +#> age sex weight height muac oedema +#> 1 54 1 20.5 111.5 180 2 +#> 4 44 1 18.9 111.0 163 2 +#> 7 55 1 18.6 109.3 156 2 +#> 14 48 1 17.0 109.0 175 2 +#> 27 56 2 16.4 110.0 149 2 +#> 62 55 1 15.1 118.0 156 2 +#> 66 56 1 15.0 115.0 148 2 +#> 113 58 1 14.2 92.0 148 2 +#> 129 23 2 14.0 95.0 161 2 +#> 190 15 2 13.0 90.5 150 2 +#> 212 21 2 12.8 93.0 152 2 +#> 378 51 2 11.2 83.0 141 2 +#> 453 49 1 10.6 83.0 139 2 +#> 461 54 2 10.5 86.8 132 2 +#> 551 41 2 9.8 78.0 139 2 +#> 599 50 1 9.5 84.7 123 2 +#> 660 49 1 9.1 79.5 129 2 +#> 722 17 2 8.6 63.3 136 2 +#> 809 41 2 7.9 75.7 120 2 +#> 881 30 1 6.5 69.6 103 2 +#> 893 18 2 5.8 63.2 106 2 +

The Mahalanobis distance method is usually robust enough to deal with +age data provided an appropriate value for alpha is +used.

-
-

-Difficult relationships for the Mahalanobis distance method

-

The Mahalanobis distance method works well with pairs of variables as long as the relationship between the two variables is monotonic (i.e. one variables always increases or always decreases in value as the other variable increases in value). This is usually the case with anthropometric data.

-

We will explore the use of the Mahalanobis distance method with data that is not monotonic using generated data:

+
+

Difficult relationships for the Mahalanobis distance method +

+

The Mahalanobis distance method works well with pairs of variables as +long as the relationship between the two variables is monotonic +(i.e. one variables always increases or always decreases in value as the +other variable increases in value). This is usually the case with +anthropometric data.

+

We will explore the use of the Mahalanobis distance method with data +that is not monotonic using generated data:

-x <- c(4, 8, 16, 17, 22, 27, 38, 40, 47, 48, 53, 55, 63, 71, 76, 85, 92, 96) 
-y <- c(6, 22, 34, 42, 51, 59, 64, 69, 70, 20, 70, 63, 63, 55, 46, 33, 19, 6)
-plot(x, y)
+x <- c(4, 8, 16, 17, 22, 27, 38, 40, 47, 48, 53, 55, 63, 71, 76, 85, 92, 96) +y <- c(6, 22, 34, 42, 51, 59, 64, 69, 70, 20, 70, 63, 63, 55, 46, 33, 19, 6) +plot(x, y)

-

There is a clear relationship between x and y but it is not a monotonic relationship (i.e. it is not always increasing or decreasing). There is a single obvious outlier. The Mahalanobis distance method will not work well with this data. This:

+

There is a clear relationship between x and +y but it is not a monotonic +relationship (i.e. it is not always increasing or decreasing). There is +a single obvious outlier. The Mahalanobis distance method will +not work well with this data. This:

-plot(x, y, pch = ifelse(outliersMD(x, y), 19, 1))
+plot(x, y, pch = ifelse(outliersMD(x, y), 19, 1))

fails to detect the outlier. Relaxing the alpha parameter:

-plot(x, y, pch = ifelse(outliersMD(x, y, alpha = 0.025), 19, 1))
+plot(x, y, pch = ifelse(outliersMD(x, y, alpha = 0.025), 19, 1))

does not help. Relaxing the alpha parameter further:

-plot(x, y, pch = ifelse(outliersMD(x, y, alpha = 0.1), 19, 1))
+plot(x, y, pch = ifelse(outliersMD(x, y, alpha = 0.1), 19, 1))

-

results in false positive results but fails to identify the clear outlier.

-

Although Mahalanobis distance cannot be used directly to identify outliers in non-monotonic relationships, it can be applied to residuals from fitted non-linear models. This technique is unlikely to be required with anthropometric data and is not covered in this toolkit.

-

It is very unlikely that you will see non-monotonic relationships with anthropometric data. You are likely to see “growth curves” that look like this:

+

results in false positive results but fails to identify the clear +outlier.

+

Although Mahalanobis distance cannot be used directly to identify +outliers in non-monotonic relationships, it can be applied to residuals +from fitted non-linear models. This technique is unlikely to be required +with anthropometric data and is not covered in this toolkit.

+

It is very unlikely that you will see non-monotonic relationships +with anthropometric data. You are likely to see “growth curves” that +look like this:

-set.seed(0)
-x <- 0:100
-y <- 1 - exp(-x / 50) + rnorm(101, 0, 0.05) 
-plot(x, y)
-lines(x, 1 - exp(-x / 50), lty = 2)
+set.seed(0) +x <- 0:100 +y <- 1 - exp(-x / 50) + rnorm(101, 0, 0.05) +plot(x, y) +lines(x, 1 - exp(-x / 50), lty = 2)

-

This is a monotonic relationship. The Mahalanobis distance method should work well with this data. If we add a clear outlier:

+

This is a monotonic relationship. The Mahalanobis distance method +should work well with this data. If we add a clear outlier:

-y[50] <- 0.3
-plot(x, y)
+y[50] <- 0.3 +plot(x, y)

-

this can be detected using the Mahalanobis distance method using a slightly relaxed alpha value:

+

this can be detected using the Mahalanobis distance method using a +slightly relaxed alpha value:

-plot(x, y, pch = ifelse(outliersMD(x, y, alpha = 0.005), 19, 1))
+plot(x, y, pch = ifelse(outliersMD(x, y, alpha = 0.005), 19, 1))

-
-

-Working with data from older children

-

We will now look at using scatterplots and Mahalanobis distance methods with data from older children.

+
+

Working with data from older children +

+

We will now look at using scatterplots and Mahalanobis distance +methods with data from older children.

We will use the sp.ex02 dataset:

-svy <- sp.ex02
-head(svy)
-#>   region school ageMonths sex weight height   haz   waz   baz
-#> 1      1      1        64   1   13.9   97.7 -3.12 -2.56 -0.56
-#> 2      1      1        72   1   21.1  118.7  0.56  0.21 -0.25
-#> 3      1      1        75   2   15.6  103.7 -2.47 -2.02 -0.53
-#> 4      1      1        75   2   16.0  102.7 -2.66 -1.82 -0.08
-#> 5      1      1        75   2   17.5  108.7 -1.51 -1.16 -0.31
-#> 6      1      1        79   1   15.0  101.0 -3.57 -2.99 -0.53
-

The dataset sp.ex02 contains anthropometric data from a survey of school-age (i.e. between 5 and 15 years) children from Pakistan.

+svy <- sp.ex02 +head(svy) +#> region school ageMonths sex weight height haz waz baz +#> 1 1 1 64 1 13.9 97.7 -3.12 -2.56 -0.56 +#> 2 1 1 72 1 21.1 118.7 0.56 0.21 -0.25 +#> 3 1 1 75 2 15.6 103.7 -2.47 -2.02 -0.53 +#> 4 1 1 75 2 16.0 102.7 -2.66 -1.82 -0.08 +#> 5 1 1 75 2 17.5 108.7 -1.51 -1.16 -0.31 +#> 6 1 1 79 1 15.0 101.0 -3.57 -2.99 -0.53
+

The dataset sp.ex02 contains anthropometric data from a +survey of school-age (i.e. between 5 and 15 years) children from +Pakistan.

We can summarise the dataset using:

-summary(svy)
+summary(svy)

This returns:

-
#>      region          school        ageMonths          sex       
-#>  Min.   :1.000   Min.   : 1.00   Min.   : 60.0   Min.   :1.000  
-#>  1st Qu.:3.000   1st Qu.: 8.00   1st Qu.: 83.0   1st Qu.:1.000  
-#>  Median :4.000   Median :15.00   Median : 98.0   Median :1.000  
-#>  Mean   :4.491   Mean   :15.51   Mean   :104.8   Mean   :1.397  
-#>  3rd Qu.:7.000   3rd Qu.:23.00   3rd Qu.:124.0   3rd Qu.:2.000  
-#>  Max.   :8.000   Max.   :30.00   Max.   :178.0   Max.   :2.000  
-#>                                                                 
-#>      weight          height           haz              waz        
-#>  Min.   :10.30   Min.   : 86.2   Min.   :-5.730   Min.   :-5.350  
-#>  1st Qu.:17.20   1st Qu.:108.7   1st Qu.:-2.640   1st Qu.:-2.380  
-#>  Median :21.30   Median :120.9   Median :-1.790   Median :-1.615  
-#>  Mean   :22.62   Mean   :121.2   Mean   :-1.705   Mean   :-1.581  
-#>  3rd Qu.:27.00   3rd Qu.:132.6   3rd Qu.:-0.790   3rd Qu.:-0.805  
-#>  Max.   :51.90   Max.   :164.2   Max.   : 3.550   Max.   : 3.010  
-#>                                                   NA's   :267     
-#>       baz         
-#>  Min.   :-4.7000  
-#>  1st Qu.:-1.2900  
-#>  Median :-0.7600  
-#>  Mean   :-0.7758  
-#>  3rd Qu.:-0.2100  
-#>  Max.   : 1.9900  
-#>  NA's   :8
-

The baz variable contains the BMI-for-age z-score calculated from the ageMonths, sex, weight, and height variables using the WHO growth reference. A key thing to notice in the summary is the large number of missing values in the waz variable. This is because the weight-for-age z-score is not calculated for children aged older than 120 months. You can check this using:

+
#>      region          school        ageMonths          sex       
+#>  Min.   :1.000   Min.   : 1.00   Min.   : 60.0   Min.   :1.000  
+#>  1st Qu.:3.000   1st Qu.: 8.00   1st Qu.: 83.0   1st Qu.:1.000  
+#>  Median :4.000   Median :15.00   Median : 98.0   Median :1.000  
+#>  Mean   :4.491   Mean   :15.51   Mean   :104.8   Mean   :1.397  
+#>  3rd Qu.:7.000   3rd Qu.:23.00   3rd Qu.:124.0   3rd Qu.:2.000  
+#>  Max.   :8.000   Max.   :30.00   Max.   :178.0   Max.   :2.000  
+#>                                                                 
+#>      weight          height           haz              waz        
+#>  Min.   :10.30   Min.   : 86.2   Min.   :-5.730   Min.   :-5.350  
+#>  1st Qu.:17.20   1st Qu.:108.7   1st Qu.:-2.640   1st Qu.:-2.380  
+#>  Median :21.30   Median :120.9   Median :-1.790   Median :-1.615  
+#>  Mean   :22.62   Mean   :121.2   Mean   :-1.705   Mean   :-1.581  
+#>  3rd Qu.:27.00   3rd Qu.:132.6   3rd Qu.:-0.790   3rd Qu.:-0.805  
+#>  Max.   :51.90   Max.   :164.2   Max.   : 3.550   Max.   : 3.010  
+#>                                                   NA's   :267     
+#>       baz         
+#>  Min.   :-4.7000  
+#>  1st Qu.:-1.2900  
+#>  Median :-0.7600  
+#>  Mean   :-0.7758  
+#>  3rd Qu.:-0.2100  
+#>  Max.   : 1.9900  
+#>  NA's   :8
+

The baz variable contains the BMI-for-age z-score +calculated from the ageMonths, sex, +weight, and height variables using the +WHO growth reference. A key thing to notice in the summary is the large +number of missing values in the waz variable. This is +because the weight-for-age z-score is not calculated for children aged +older than 120 months. You can check this using:

-by(svy$ageMonths, is.na(svy$waz), summary)
+by(svy$ageMonths, is.na(svy$waz), summary)

This gives:

-
#> is.na(svy$waz): FALSE
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>   60.00   76.00   88.00   88.24   99.00  120.00 
-#> ------------------------------------------------------------ 
-#> is.na(svy$waz): TRUE
-#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-#>   121.0   125.5   141.0   140.8   151.0   178.0
-

There appears to be nothing odd about the large number of missing values in the waz variable.

-

We should investigate the missing values in the baz variable:

+
#> is.na(svy$waz): FALSE
+#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
+#>   60.00   76.00   88.00   88.24   99.00  120.00 
+#> ------------------------------------------------------------ 
+#> is.na(svy$waz): TRUE
+#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
+#>   121.0   125.5   141.0   140.8   151.0   178.0
+

There appears to be nothing odd about the large number of missing +values in the waz variable.

+

We should investigate the missing values in the baz +variable:

-svy[is.na(svy$baz), ]
+svy[is.na(svy$baz), ]

This returns:

-
#>     region school ageMonths sex weight height   haz   waz baz
-#> 83       1      3       143   2   14.0  125.9 -3.64    NA  NA
-#> 158      2      6        96   1   12.3  118.4 -1.57 -5.26  NA
-#> 275      3     10        77   1   10.3  113.9 -0.88 -5.35  NA
-#> 415      4     15        75   1   33.0  108.3 -1.90  3.01  NA
-#> 508      5     19        85   2   11.1  111.5 -1.78 -4.84  NA
-#> 529      6     20        78   1   12.1  111.9 -1.37 -4.45  NA
-#> 761      8     28        62   1   13.3  115.4  0.99 -2.70  NA
-#> 806      8     29       100   1   13.2  121.2 -1.36 -5.01  NA
-

The data required to calculate the BMI-for-age z-score are present. Given the extreme values in the waz variable it is likely that the BMI-for-age z-scores in these records were calculated, found to be outside of the upper and lower flagging criteria, and the value for baz set to missing. We should check this and recalculate the BMI-for-age z-scores.

-

We can use scatterplots to examine the relationship between ageMonths, weight, and height:

+
#>     region school ageMonths sex weight height   haz   waz baz
+#> 83       1      3       143   2   14.0  125.9 -3.64    NA  NA
+#> 158      2      6        96   1   12.3  118.4 -1.57 -5.26  NA
+#> 275      3     10        77   1   10.3  113.9 -0.88 -5.35  NA
+#> 415      4     15        75   1   33.0  108.3 -1.90  3.01  NA
+#> 508      5     19        85   2   11.1  111.5 -1.78 -4.84  NA
+#> 529      6     20        78   1   12.1  111.9 -1.37 -4.45  NA
+#> 761      8     28        62   1   13.3  115.4  0.99 -2.70  NA
+#> 806      8     29       100   1   13.2  121.2 -1.36 -5.01  NA
+

The data required to calculate the BMI-for-age z-score are present. +Given the extreme values in the waz variable it is +likely that the BMI-for-age z-scores in these records were calculated, +found to be outside of the upper and lower flagging criteria, and the +value for baz set to missing. We should check this and +recalculate the BMI-for-age z-scores.

+

We can use scatterplots to examine the relationship between +ageMonths, weight, and +height:

-plot(svy$ageMonths, svy$weight) 
+plot(svy$ageMonths, svy$weight)

-plot(svy$ageMonths, svy$height) 
+plot(svy$ageMonths, svy$height)

-plot(svy$height, svy$weight)
+plot(svy$height, svy$weight)

These relationships are not as simple as in younger children:

-

Variability in weight appears to increase with increasing ageMonths.

-

The relationship between height and ageMonths may not be entirely linear.

-

The relationship between weight and height is clearly non-linear.

-

All of these relationships are monotonic so we should still be able to use the Mahalanobis distance method to identify outliers:

+

Variability in weight appears to increase with +increasing ageMonths.

+

The relationship between height and +ageMonths may not be entirely linear.

+

The relationship between weight and +height is clearly non-linear.

+

All of these relationships are monotonic so we should still be able +to use the Mahalanobis distance method to identify outliers:

-plot(svy$ageMonths, svy$weight,
-     pch = ifelse(outliersMD(svy$ageMonths, svy$weight), 19, 1))
+plot(svy$ageMonths, svy$weight, + pch = ifelse(outliersMD(svy$ageMonths, svy$weight), 19, 1))

-
-plot(svy$ageMonths, svy$height,
-     pch = ifelse(outliersMD(svy$ageMonths, svy$height), 19, 1))
+ +plot(svy$ageMonths, svy$height, + pch = ifelse(outliersMD(svy$ageMonths, svy$height), 19, 1))

-
-plot(svy$height, svy$weight,
-     pch = ifelse(outliersMD(svy$height, svy$weight), 19, 1))
+ +plot(svy$height, svy$weight, + pch = ifelse(outliersMD(svy$height, svy$weight), 19, 1))

-

You may want to experiment with different values of the alpha parameter of the outliersMD() function as described above. Records containing values identified as outliers can be listed:

+

You may want to experiment with different values of the +alpha parameter of the outliersMD() +function as described above. Records containing values identified as +outliers can be listed:

-svy[outliersMD(svy$ageMonths, svy$weight), ] 
-#>     region school ageMonths sex weight height   haz  waz   baz
-#> 57       1      2       161   1   47.0  158.7 -0.05   NA  0.05
-#> 83       1      3       143   2   14.0  125.9 -3.64   NA    NA
-#> 139      2      5       123   2   46.5  144.9  0.64   NA  1.82
-#> 319      3     11       143   1   45.2  156.0  1.06   NA  0.50
-#> 407      4     14       132   1   46.2  155.3  1.73   NA  0.97
-#> 415      4     15        75   1   33.0  108.3 -1.90 3.01    NA
-#> 672      7     24       175   1   50.5  163.5 -0.42   NA -0.25
-#> 727      7     26       147   1   46.1  162.7  1.67   NA -0.14
-#> 731      7     26       173   1   51.9  164.2 -0.21   NA -0.03
-svy[outliersMD(svy$ageMonths, svy$height), ] 
-#>     region school ageMonths sex weight height  haz  waz   baz
-#> 457      5     17       110   1   37.5    155 3.55 1.62 -0.32
-svy[outliersMD(svy$weight, svy$height), ]
-#>     region school ageMonths sex weight height   haz   waz   baz
-#> 57       1      2       161   1   47.0  158.7 -0.05    NA  0.05
-#> 83       1      3       143   2   14.0  125.9 -3.64    NA    NA
-#> 139      2      5       123   2   46.5  144.9  0.64    NA  1.82
-#> 275      3     10        77   1   10.3  113.9 -0.88 -5.35    NA
-#> 319      3     11       143   1   45.2  156.0  1.06    NA  0.50
-#> 322      3     11       155   2   39.0  135.5 -3.01    NA  0.84
-#> 369      4     13       118   2   35.5  129.7 -1.32  0.66  1.66
-#> 407      4     14       132   1   46.2  155.3  1.73    NA  0.97
-#> 415      4     15        75   1   33.0  108.3 -1.90  3.01    NA
-#> 438      4     15       163   2   39.0  138.3 -2.92    NA  0.41
-#> 611      6     22       148   1   41.9  146.5 -0.66    NA  0.75
-#> 672      7     24       175   1   50.5  163.5 -0.42    NA -0.25
-#> 731      7     26       173   1   51.9  164.2 -0.21    NA -0.03
-#> 806      8     29       100   1   13.2  121.2 -1.36 -5.01    NA
-

These records can be checked, edited (if required), and anthropometric indices recalculated.

+svy[outliersMD(svy$ageMonths, svy$weight), ] +#> region school ageMonths sex weight height haz waz baz +#> 57 1 2 161 1 47.0 158.7 -0.05 NA 0.05 +#> 83 1 3 143 2 14.0 125.9 -3.64 NA NA +#> 139 2 5 123 2 46.5 144.9 0.64 NA 1.82 +#> 319 3 11 143 1 45.2 156.0 1.06 NA 0.50 +#> 407 4 14 132 1 46.2 155.3 1.73 NA 0.97 +#> 415 4 15 75 1 33.0 108.3 -1.90 3.01 NA +#> 672 7 24 175 1 50.5 163.5 -0.42 NA -0.25 +#> 727 7 26 147 1 46.1 162.7 1.67 NA -0.14 +#> 731 7 26 173 1 51.9 164.2 -0.21 NA -0.03 +svy[outliersMD(svy$ageMonths, svy$height), ] +#> region school ageMonths sex weight height haz waz baz +#> 457 5 17 110 1 37.5 155 3.55 1.62 -0.32 +svy[outliersMD(svy$weight, svy$height), ] +#> region school ageMonths sex weight height haz waz baz +#> 57 1 2 161 1 47.0 158.7 -0.05 NA 0.05 +#> 83 1 3 143 2 14.0 125.9 -3.64 NA NA +#> 139 2 5 123 2 46.5 144.9 0.64 NA 1.82 +#> 275 3 10 77 1 10.3 113.9 -0.88 -5.35 NA +#> 319 3 11 143 1 45.2 156.0 1.06 NA 0.50 +#> 322 3 11 155 2 39.0 135.5 -3.01 NA 0.84 +#> 369 4 13 118 2 35.5 129.7 -1.32 0.66 1.66 +#> 407 4 14 132 1 46.2 155.3 1.73 NA 0.97 +#> 415 4 15 75 1 33.0 108.3 -1.90 3.01 NA +#> 438 4 15 163 2 39.0 138.3 -2.92 NA 0.41 +#> 611 6 22 148 1 41.9 146.5 -0.66 NA 0.75 +#> 672 7 24 175 1 50.5 163.5 -0.42 NA -0.25 +#> 731 7 26 173 1 51.9 164.2 -0.21 NA -0.03 +#> 806 8 29 100 1 13.2 121.2 -1.36 -5.01 NA +

These records can be checked, edited (if required), and +anthropometric indices recalculated.

- - - - + -
+ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-10-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-10-1.png index a00a876..b959281 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-10-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-10-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-22-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-22-1.png index f5f5e5d..40193ac 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-22-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-25-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-25-1.png index 5808825..79d621d 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-25-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-25-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-28-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-28-1.png index ca6733a..4eaa381 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-28-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-28-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-29-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-29-1.png index 9841b6d..744f3bb 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-29-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-29-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-31-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-31-1.png index 7b646a4..40c051f 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-31-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-31-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-32-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-32-1.png index 7b646a4..40c051f 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-32-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-32-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-34-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-34-1.png index 72aaa05..e095e65 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-34-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-34-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-35-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-35-1.png index 54c7d83..b1d8f53 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-35-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-35-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-36-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-36-1.png index a8602d1..75fbfdf 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-36-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-36-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-37-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-37-1.png index 8498486..2a10311 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-37-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-37-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-4-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-4-1.png index bffe33a..aef457c 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-4-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-4-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-45-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-45-1.png index a816e7e..8a28c97 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-45-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-45-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-45-2.png b/docs/articles/sp_files/figure-html/unnamed-chunk-45-2.png index 3550e21..43aaa94 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-45-2.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-45-2.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-45-3.png b/docs/articles/sp_files/figure-html/unnamed-chunk-45-3.png index 1849b6f..18bf040 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-45-3.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-45-3.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-46-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-46-1.png index 04af31a..9b03c13 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-46-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-46-1.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-46-2.png b/docs/articles/sp_files/figure-html/unnamed-chunk-46-2.png index 9efa213..64c5242 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-46-2.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-46-2.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-46-3.png b/docs/articles/sp_files/figure-html/unnamed-chunk-46-3.png index 7a9f2d7..95b960a 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-46-3.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-46-3.png differ diff --git a/docs/articles/sp_files/figure-html/unnamed-chunk-8-1.png b/docs/articles/sp_files/figure-html/unnamed-chunk-8-1.png index fe0ecbd..b052bd8 100644 Binary files a/docs/articles/sp_files/figure-html/unnamed-chunk-8-1.png and b/docs/articles/sp_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/docs/articles/sr.html b/docs/articles/sr.html index 9700387..f8af81b 100644 --- a/docs/articles/sr.html +++ b/docs/articles/sr.html @@ -4,7 +4,8 @@ - + + Sex ratio • nipnTK @@ -12,271 +13,267 @@ - - - + + + - - + + - -
-
- +
-
- +
#>   psu age sex weight height muac oedema
+#> 1   1   6   1    7.3   65.0  146      2
+#> 2   1  42   2   12.5   89.5  156      2
+#> 3   1  23   1   10.6   78.1  149      2
+#> 4   1  18   1   12.8   81.5  160      2
+#> 5   1  52   1   12.1   87.3  152      2
+#> 6   1  36   2   16.9   93.0  190      2
+

The dataset dp.ex02.csv is a comma-separated-value (CSV) +file containing anthropometric data from a SMART survey in Kabul, +Afghanistan.

+

It is reported that there are about 2.658 million boys and 2.508 +million girls aged between zero and four years in Afghanistan (2012 +estimates).

The male to female sex ratio is:

-2.658 / 2.508
+2.658 / 2.508

which is:

-
#> [1] 1.059809
-

It is often easier to work with the proportion of the population that is male:

+
#> [1] 1.059809
+

It is often easier to work with the proportion of the population that +is male:

-2.658 / (2.658 + 2.508)
+2.658 / (2.658 + 2.508)

which is:

-
#> [1] 0.514518
+
#> [1] 0.514518

We compare this to the proportion of the sample that is male:

-table(svy$sex)
+table(svy$sex)

this gives:

-
#> 
-#>   1   2 
-#> 438 435
-

This table is more useful when the cell counts are expressed as proportions:

+
#> 
+#>   1   2 
+#> 438 435
+

This table is more useful when the cell counts are expressed as +proportions:

-prop.table(table(svy$sex))
+prop.table(table(svy$sex))

this gives:

-
#> 
-#>         1         2 
-#> 0.5017182 0.4982818
+
#> 
+#>         1         2 
+#> 0.5017182 0.4982818

A formal test can be made:

-prop.test(table(svy$sex), p = 0.514518)
+prop.test(table(svy$sex), p = 0.514518)

This returns:

-
#> 
-#>  1-sample proportions test with continuity correction
-#> 
-#> data:  table(svy$sex), null probability 0.514518
-#> X-squared = 0.5225, df = 1, p-value = 0.4698
-#> alternative hypothesis: true p is not equal to 0.514518
-#> 95 percent confidence interval:
-#>  0.4680459 0.5353752
-#> sample estimates:
-#>         p 
-#> 0.5017182
-

The male to female sex ratio (expressed as the proportion male) in the example data is not significantly different from the expected male to female sex ratio (expressed as the proportion male).

-

The NiPN data quality toolkit provides an R language function called sexRatioTest() that performs a sex ratio test:

+
#> 
+#>  1-sample proportions test with continuity correction
+#> 
+#> data:  table(svy$sex), null probability 0.514518
+#> X-squared = 0.5225, df = 1, p-value = 0.4698
+#> alternative hypothesis: true p is not equal to 0.514518
+#> 95 percent confidence interval:
+#>  0.4680459 0.5353752
+#> sample estimates:
+#>         p 
+#> 0.5017182
+

The male to female sex ratio (expressed as the proportion male) in +the example data is not significantly different from the expected male +to female sex ratio (expressed as the proportion male).

+

The NiPN data quality toolkit provides an R language function called +sexRatioTest() that performs a sex ratio test:

-sexRatioTest(svy$sex, codes = c(1, 2), pop = c(2.658, 2.508))
+sexRatioTest(svy$sex, codes = c(1, 2), pop = c(2.658, 2.508))

which returns:

-
#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.5017
-#> X-squared = 0.5225, p = 0.4698
-

The codes used in the sex variable for male and female are specified using the codes parameter. If (e.g.) sex were coded using M and F then you would specify codes = c("M", "F").

-

Population data are specified using the pop parameter (males then females). This can be specified as numbers or as a ratio. The test above could have been specified as:

+
#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.5145
+#> Observed proportion male = 0.5017
+#> X-squared = 0.5225, p = 0.4698
+

The codes used in the sex variable for male and female are specified +using the codes parameter. If (e.g.) sex were coded using +M and F then you would specify +codes = c("M", "F").

+

Population data are specified using the pop parameter +(males then females). This can be specified as numbers or as a ratio. +The test above could have been specified as:

-sexRatioTest(svy$sex, codes = c(1, 2), pop = c(1.059809, 1))
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.5017
-#> X-squared = 0.5225, p = 0.4698
-

If (e.g.) you want to specify a one to one sex ratio then you would use pop = c(1, 1).

-

The observed sex ratio at birth is 1.06:1.00 (males to females). This could be used to assess if selective abortion or female infanticide is taking place although a large sample size (i.e. about n = 6200) is required for such a test to have sufficient power.

-
-

-Analysis by age

-

The sex ratio test may be performed on each age group separately. You can apply the sex ratio test to each age-group using the by() function:

+sexRatioTest(svy$sex, codes = c(1, 2), pop = c(1.059809, 1)) +#> +#> Sex Ratio Test +#> +#> Expected proportion male = 0.5145 +#> Observed proportion male = 0.5017 +#> X-squared = 0.5225, p = 0.4698
+

If (e.g.) you want to specify a one to one sex ratio then you would +use pop = c(1, 1).

+

The observed sex ratio at birth is 1.06:1.00 (males to females). This +could be used to assess if selective abortion or female infanticide is +taking place although a large sample size (i.e. about n = 6200) is +required for such a test to have sufficient power.

+
+

Analysis by age +

+

The sex ratio test may be performed on each age group separately. You +can apply the sex ratio test to each age-group using the +by() function:

-svy$ycag <- recode(svy$age, "6:17=1; 18:29=2; 30:41=3; 42:53=4; 54:59=5") 
-by(svy$sex, svy$ycag, sexRatioTest, codes = c(1, 2), pop = c(2.658, 2.508))
-#> svy$ycag: 1
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.4879
-#> X-squared = 0.4845, p = 0.4864
-#> 
-#> ------------------------------------------------------------ 
-#> svy$ycag: 2
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.5152
-#> X-squared = 0.0000, p = 1.0000
-#> 
-#> ------------------------------------------------------------ 
-#> svy$ycag: 3
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.5228
-#> X-squared = 0.0374, p = 0.8466
-#> 
-#> ------------------------------------------------------------ 
-#> svy$ycag: 4
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.4875
-#> X-squared = 0.3657, p = 0.5454
-#> 
-#> ------------------------------------------------------------ 
-#> svy$ycag: 5
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5145
-#> Observed proportion male = 0.4627
-#> X-squared = 0.5280, p = 0.4674
-

Note that the variable ycag created above holds the year-centred-age-group.

+svy$ycag <- recode(svy$age, "6:17=1; 18:29=2; 30:41=3; 42:53=4; 54:59=5") +by(svy$sex, svy$ycag, sexRatioTest, codes = c(1, 2), pop = c(2.658, 2.508)) +#> svy$ycag: 1 +#> +#> Sex Ratio Test +#> +#> Expected proportion male = 0.5145 +#> Observed proportion male = 0.4879 +#> X-squared = 0.4845, p = 0.4864 +#> +#> ------------------------------------------------------------ +#> svy$ycag: 2 +#> +#> Sex Ratio Test +#> +#> Expected proportion male = 0.5145 +#> Observed proportion male = 0.5152 +#> X-squared = 0.0000, p = 1.0000 +#> +#> ------------------------------------------------------------ +#> svy$ycag: 3 +#> +#> Sex Ratio Test +#> +#> Expected proportion male = 0.5145 +#> Observed proportion male = 0.5228 +#> X-squared = 0.0374, p = 0.8466 +#> +#> ------------------------------------------------------------ +#> svy$ycag: 4 +#> +#> Sex Ratio Test +#> +#> Expected proportion male = 0.5145 +#> Observed proportion male = 0.4875 +#> X-squared = 0.3657, p = 0.5454 +#> +#> ------------------------------------------------------------ +#> svy$ycag: 5 +#> +#> Sex Ratio Test +#> +#> Expected proportion male = 0.5145 +#> Observed proportion male = 0.4627 +#> X-squared = 0.5280, p = 0.4674
+

Note that the variable ycag created above holds the +year-centred-age-group.

This approach assumes that the sex ratio is independent of age.

-

An approach that does not make this assumption is to use the numbers of male and female children in the same age-ranges in the population taken from census data.

-

A useful source of census data is the United States Census Bureau’s International Data Base:

-

https://www.census.gov/data-tools/demo/idb/informationGateway.php

-

This source gives the following estimates for Afghanistan in 2016:

- +

An approach that does not make this assumption is to use the numbers +of male and female children in the same age-ranges in the population +taken from census data.

+

A useful source of census data is the United +States Census Bureau’s International Data Base:

+

https://www.census.gov/data-tools/demo/idb/informationGateway.php

+

This source gives the following estimates for Afghanistan in +2016:

+
Age @@ -402,120 +399,140 @@

We need to ensure we use the same age-ranges as the census:

-svy$ageGroup <- recode(svy$age, "0:11=0; 12:23=1; 24:35=2; 36:47=3; 48:59=4")
+svy$ageGroup <- recode(svy$age, "0:11=0; 12:23=1; 24:35=2; 36:47=3; 48:59=4")

and then test the sex ratio in each age group separately:

-
sexRatioTest(svy$sex[svy$ageGroup == 0], pop = c(594602, 573956)) sexRatioTest(svy$sex[svy$ageGroup == 1], pop = c(550593, 533579)) sexRatioTest(svy$sex[svy$ageGroup == 2], pop = c(526827, 510479)) sexRatioTest(svy$sex[svy$ageGroup == 3], pop = c(509048, 493185)) sexRatioTest(svy$sex[svy$ageGroup == 4], pop = c(493521, 478137))
-
#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5088
-#> Observed proportion male = 0.5047
-#> X-squared = 0.0000, p = 1.0000
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5078
-#> Observed proportion male = 0.4901
-#> X-squared = 0.1885, p = 0.6642
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5079
-#> Observed proportion male = 0.5374
-#> X-squared = 0.6800, p = 0.4096
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5079
-#> Observed proportion male = 0.5052
-#> X-squared = 0.0000, p = 0.9978
-#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.5079
-#> Observed proportion male = 0.4552
-#> X-squared = 1.4098, p = 0.2351
-

All of these tests find no significant differences between the observed and expected sex ratios. It should be noted that some (or all) of the tests might be based on small sample sizes:

+
sexRatioTest(svy$sex[svy$ageGroup == 0], pop = c(594602, 573956)) sexRatioTest(svy$sex[svy$ageGroup == 1], pop = c(550593, 533579)) sexRatioTest(svy$sex[svy$ageGroup == 2], pop = c(526827, 510479)) sexRatioTest(svy$sex[svy$ageGroup == 3], pop = c(509048, 493185)) sexRatioTest(svy$sex[svy$ageGroup == 4], pop = c(493521, 478137))
+
#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.5088
+#> Observed proportion male = 0.5047
+#> X-squared = 0.0000, p = 1.0000
+#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.5078
+#> Observed proportion male = 0.4901
+#> X-squared = 0.1885, p = 0.6642
+#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.5079
+#> Observed proportion male = 0.5374
+#> X-squared = 0.6800, p = 0.4096
+#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.5079
+#> Observed proportion male = 0.5052
+#> X-squared = 0.0000, p = 0.9978
+#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.5079
+#> Observed proportion male = 0.4552
+#> X-squared = 1.4098, p = 0.2351
+

All of these tests find no significant differences between the +observed and expected sex ratios. It should be noted that some (or all) +of the tests might be based on small sample sizes:

-table(svy$ageGroup)
-#> 
-#>   0   1   2   3   4 
-#> 107 202 227 192 145
+table(svy$ageGroup) +#> +#> 0 1 2 3 4 +#> 107 202 227 192 145

and may, therefore, be able to detect only large differences.

-
-

-Sex ratios in adults

-

With data from children we usually expect something like a one to one male to female sex ratio. This will not usually be the case with adults, especially older adults.

+
+

Sex ratios in adults +

+

With data from children we usually expect something like a one to one +male to female sex ratio. This will not usually be the case with adults, +especially older adults.

We will retrieve a survey dataset:

-svy <- read.table("ah.ex01.csv", header = TRUE, sep = ",") 
-head(svy)
-
#>   psu camp block age sex weight height demispan muac oedema
-#> 1   1  IFO   A01  90   1   40.8  159.3     77.2 20.0      2
-#> 2   1  IFO   A01  60   2   69.8  155.3     78.3 35.3      2
-#> 3   1  IFO   A01  63   2   51.7  156.8     80.5 25.5      2
-#> 4   1  IFO   A01  74   2   61.1  158.9     83.5 27.0      2
-#> 5   1  IFO   A01  65   2   55.1  156.9     85.5 24.5      2
-#> 6   1  IFO   A01  62   2   56.7  158.1     86.3 26.1      2
-

The dataset ah.ex01 is a comma-separated-value (CSV) file containing anthropometry data from a Rapid Assessment Method for Older People (RAM-OP) survey in the Dadaab refugee camps in Garissa, Kenya. This is a survey of older people, defined as people aged sixty years and older.

-

With this type of survey it is usually possible to use camp administration data to find the expected male to female sex ratio. This information was not given in the RAM-OP survey report.

-

The camp population is predominantly Somali. It is reported that there are 188 thousand men and 220 thousand women aged sixty years and older in Somalia (2010 estimates). The sex ratio is:

+svy <- read.table("ah.ex01.csv", header = TRUE, sep = ",") +head(svy)
+
#>   psu camp block age sex weight height demispan muac oedema
+#> 1   1  IFO   A01  90   1   40.8  159.3     77.2 20.0      2
+#> 2   1  IFO   A01  60   2   69.8  155.3     78.3 35.3      2
+#> 3   1  IFO   A01  63   2   51.7  156.8     80.5 25.5      2
+#> 4   1  IFO   A01  74   2   61.1  158.9     83.5 27.0      2
+#> 5   1  IFO   A01  65   2   55.1  156.9     85.5 24.5      2
+#> 6   1  IFO   A01  62   2   56.7  158.1     86.3 26.1      2
+

The dataset ah.ex01 is a comma-separated-value (CSV) +file containing anthropometry data from a Rapid Assessment Method for +Older People (RAM-OP) survey in the Dadaab refugee camps in Garissa, +Kenya. This is a survey of older people, defined as people aged sixty +years and older.

+

With this type of survey it is usually possible to use camp +administration data to find the expected male to female sex ratio. This +information was not given in the RAM-OP survey report.

+

The camp population is predominantly Somali. It is reported that +there are 188 thousand men and 220 thousand women aged sixty years and +older in Somalia (2010 estimates). The sex ratio is:

-188 / 220
+188 / 220

which is:

-
#> [1] 0.8545455
+
#> [1] 0.8545455

The expected proportion of the population that is male is:

-188 / (188 + 220)
+188 / (188 + 220)

which is:

-
#> [1] 0.4607843
+
#> [1] 0.4607843

The proportion of the sample that is male:

-prop.table(table(svy$sex))
+prop.table(table(svy$sex))

is:

-
#> 
-#>        1        2 
-#> 0.381113 0.618887
-

This looks to be much smaller than the expected proportion. The sex ratio test:

+
#> 
+#>        1        2 
+#> 0.381113 0.618887
+

This looks to be much smaller than the expected proportion. The sex +ratio test:

-sexRatioTest(svy$sex, codes = c(1, 2), pop = c(188, 220))
+sexRatioTest(svy$sex, codes = c(1, 2), pop = c(188, 220))

reports:

-
#> 
-#>  Sex Ratio Test
-#> 
-#> Expected proportion male = 0.4608
-#> Observed proportion male = 0.3811
-#> X-squared = 14.8305, p = 0.0001
-

The proportion of males in the sample is significantly smaller than we expected.

-

This result could be due to the extraordinary nature of the population (e.g. the camp population could really have very many more older women than older men). It could also due to a selection bias in the survey. In this example, men were more likely than women to be away from home during the day and a household sample taken during the day would have systematically excluded the more active members of the male population.

-

Note that the sex ratio test only applies to population surveys. If surveys focus on (e.g.) carers of small children then the observed male to female sex ratio is likely to be strongly biased towards women. In such cases it is not sensible to apply a sex ratio test.

+
#> 
+#>  Sex Ratio Test
+#> 
+#> Expected proportion male = 0.4608
+#> Observed proportion male = 0.3811
+#> X-squared = 14.8305, p = 0.0001
+

The proportion of males in the sample is significantly smaller than +we expected.

+

This result could be due to the extraordinary nature of the +population (e.g. the camp population could really have very many more +older women than older men). It could also due to a selection bias in +the survey. In this example, men were more likely than women to be away +from home during the day and a household sample taken during the day +would have systematically excluded the more active members of the male +population.

+

Note that the sex ratio test only applies to population surveys. If +surveys focus on (e.g.) carers of small children then the observed male +to female sex ratio is likely to be strongly biased towards women. In +such cases it is not sensible to apply a sex ratio test.

- - - - + -
+ diff --git a/docs/authors.html b/docs/authors.html index a8666b9..37b4a3a 100644 --- a/docs/authors.html +++ b/docs/authors.html @@ -1,217 +1,120 @@ - - - - - - - -Authors • nipnTK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Authors and Citation • nipnTK + Skip to contents + +
-
- - -
- +
+

Authors

+ +
  • +

    Mark Myatt. Author. +

    +
  • +
  • +

    Ernest Guevarra. Author, maintainer. +

    +
  • +
+ +
+

Citation

+

Source: inst/CITATION

+ +

Mark Myatt and Ernest Guevarra (2023). nipnTK: National Information Platforms for Nutrition (NiPN) Data Quality Toolkit R package version 0.1.1.9000 URL https://nutriverse.io/nipnTK/ DOI 10.5281/zenodo.4297897

+
@Manual{,
+  title = {nipnTK: National Information Platforms for Nutrition (NiPN) Data Quality Toolkit},
+  author = {{Mark Myatt} and {Ernest Guevarra}},
+  year = {2023},
+  note = {R package version 0.1.1.9000},
+  url = {https://nutriverse.io/nipnTK/},
+  doi = {10.5281/zenodo.4297897},
+}
+
+
-
- +
+ - - - + diff --git a/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js b/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js new file mode 100644 index 0000000..1d13886 --- /dev/null +++ b/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.2.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=t=>{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,g=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()},m=t=>{"function"==typeof t&&t()},_=(e,i,n=!0)=>{if(!n)return void m(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),m(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),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=N(t);return C.has(o)||(o=t),[n,s,o]}function D(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 j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.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 S(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 I(t,e,i,n){const s=e[i]||{};for(const o of Object.keys(s))if(o.includes(n)){const n=s[o];S(t,e,i,n.callable,n.delegationSelector)}}function N(t){return t=t.replace(y,""),T[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(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))I(t,l,i,e.slice(1));for(const i of Object.keys(c)){const n=i.replace(w,"");if(!a||e.includes(n)){const e=c[i];S(t,l,r,e.callable,e.delegationSelector)}}}else{if(!Object.keys(c).length)return;S(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!==N(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());let l=new Event(e,{bubbles:o,cancelable:!0});return l=j(l,i),a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function j(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}const M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};function $(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 W(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const B={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${W(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${W(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]=$(t.dataset[n])}return e},getDataAttribute:(t,e)=>$(t.getAttribute(`data-bs-${W(e)}`))};class F{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)?B.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?B.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const n of Object.keys(e)){const s=e[n],r=t[n],a=o(r)?"element":null==(i=r)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(a))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}var i}}class z extends F{constructor(t,e){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(e),H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),P.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 H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.2.2"}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 q=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;P.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class R extends z{static get NAME(){return"alert"}close(){if(P.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),P.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=R.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)}}))}}q(R,"close"),g(R);const V='[data-bs-toggle="button"]';class K extends z{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=K.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}P.on(document,"click.bs.button.data-api",V,(t=>{t.preventDefault();const e=t.target.closest(V);K.getOrCreateInstance(e).toggle()})),g(K);const Q={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)))}},X={endCallback:null,leftCallback:null,rightCallback:null},Y={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class U extends F{constructor(t,e){super(),this._element=t,t&&U.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return X}static get DefaultType(){return Y}static get NAME(){return"swipe"}dispose(){P.off(this._element,".bs.swipe")}_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(),m(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&&m(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(P.on(this._element,"pointerdown.bs.swipe",(t=>this._start(t))),P.on(this._element,"pointerup.bs.swipe",(t=>this._end(t))),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.swipe",(t=>this._start(t))),P.on(this._element,"touchmove.bs.swipe",(t=>this._move(t))),P.on(this._element,"touchend.bs.swipe",(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 G="next",J="prev",Z="left",tt="right",et="slid.bs.carousel",it="carousel",nt="active",st={ArrowLeft:tt,ArrowRight:Z},ot={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},rt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class at extends z{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=Q.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===it&&this.cycle()}static get Default(){return ot}static get DefaultType(){return rt}static get NAME(){return"carousel"}next(){this._slide(G)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(J)}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?P.one(this._element,et,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void P.one(this._element,et,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?G:J;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&&P.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",(()=>this.pause())),P.on(this._element,"mouseleave.bs.carousel",(()=>this._maybeEnableCycle()))),this._config.touch&&U.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of Q.find(".carousel-item img",this._element))P.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(Z)),rightCallback:()=>this._slide(this._directionToOrder(tt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new U(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=st[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=Q.findOne(".active",this._indicatorsElement);e.classList.remove(nt),e.removeAttribute("aria-current");const i=Q.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(nt),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===G,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>P.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r("slide.bs.carousel").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(nt),i.classList.remove(nt,c,l),this._isSliding=!1,r(et)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return Q.findOne(".active.carousel-item",this._element)}_getItems(){return Q.find(".carousel-item",this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===Z?J:G:t===Z?G:J}_orderToDirection(t){return p()?t===J?Z:tt:t===J?tt:Z}static jQueryInterface(t){return this.each((function(){const e=at.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)}))}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",(function(t){const e=n(this);if(!e||!e.classList.contains(it))return;t.preventDefault();const i=at.getOrCreateInstance(e),s=this.getAttribute("data-bs-slide-to");return s?(i.to(s),void i._maybeEnableCycle()):"next"===B.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),P.on(window,"load.bs.carousel.data-api",(()=>{const t=Q.find('[data-bs-ride="carousel"]');for(const e of t)at.getOrCreateInstance(e)})),g(at);const lt="show",ct="collapse",ht="collapsing",dt='[data-bs-toggle="collapse"]',ut={parent:null,toggle:!0},ft={parent:"(null|element)",toggle:"boolean"};class pt extends z{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const n=Q.find(dt);for(const t of n){const e=i(t),n=Q.find(e).filter((t=>t===this._element));null!==e&&n.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 ut}static get DefaultType(){return ft}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=>pt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),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(ht),this._element.classList.add(ct,lt),this._element.style[e]="",P.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);for(const t of this._triggerArray){const e=n(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(ht),this._element.classList.add(ct),P.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_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(dt);for(const e of t){const t=n(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=Q.find(":scope .collapse .collapse",this._config.parent);return Q.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=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}P.on(document,"click.bs.collapse.data-api",dt,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this),n=Q.find(e);for(const t of n)pt.getOrCreateInstance(t,{toggle:!1}).toggle()})),g(pt);var gt="top",mt="bottom",_t="right",bt="left",vt="auto",yt=[gt,mt,_t,bt],wt="start",At="end",Et="clippingParents",Tt="viewport",Ct="popper",Ot="reference",xt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+At])}),[]),kt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+At])}),[]),Lt="beforeRead",Dt="read",St="afterRead",It="beforeMain",Nt="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",$t=[Lt,Dt,St,It,Nt,Pt,jt,Mt,Ht];function Wt(t){return t?(t.nodeName||"").toLowerCase():null}function Bt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function Ft(t){return t instanceof Bt(t).Element||t instanceof Element}function zt(t){return t instanceof Bt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Bt(t).ShadowRoot||t instanceof ShadowRoot)}const Rt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Wt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Wt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Vt(t){return t.split("-")[0]}var Kt=Math.max,Qt=Math.min,Xt=Math.round;function Yt(){var t=navigator.userAgentData;return null!=t&&t.brands?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ut(){return!/^((?!chrome|android).)*safari/i.test(Yt())}function Gt(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&zt(t)&&(s=t.offsetWidth>0&&Xt(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&Xt(n.height)/t.offsetHeight||1);var r=(Ft(t)?Bt(t):window).visualViewport,a=!Ut()&&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 Jt(t){var e=Gt(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 Zt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function te(t){return Bt(t).getComputedStyle(t)}function ee(t){return["table","td","th"].indexOf(Wt(t))>=0}function ie(t){return((Ft(t)?t.ownerDocument:t.document)||window.document).documentElement}function ne(t){return"html"===Wt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||ie(t)}function se(t){return zt(t)&&"fixed"!==te(t).position?t.offsetParent:null}function oe(t){for(var e=Bt(t),i=se(t);i&&ee(i)&&"static"===te(i).position;)i=se(i);return i&&("html"===Wt(i)||"body"===Wt(i)&&"static"===te(i).position)?e:i||function(t){var e=/firefox/i.test(Yt());if(/Trident/i.test(Yt())&&zt(t)&&"fixed"===te(t).position)return null;var i=ne(t);for(qt(i)&&(i=i.host);zt(i)&&["html","body"].indexOf(Wt(i))<0;){var n=te(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 re(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function ae(t,e,i){return Kt(t,Qt(e,i))}function le(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ce(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const he={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=Vt(i.placement),l=re(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return le("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ce(t,yt))}(s.padding,i),d=Jt(o),u="y"===l?gt:bt,f="y"===l?mt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],g=r[l]-i.rects.reference[l],m=oe(o),_=m?"y"===l?m.clientHeight||0:m.clientWidth||0:0,b=p/2-g/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=ae(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)))&&Zt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function de(t){return t.split("-")[1]}var ue={top:"auto",right:"auto",bottom:"auto",left:"auto"};function fe(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,g=void 0===p?0:p,m="function"==typeof h?h({x:f,y:g}):{x:f,y:g};f=m.x,g=m.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=bt,y=gt,w=window;if(c){var A=oe(i),E="clientHeight",T="clientWidth";A===Bt(i)&&"static"!==te(A=ie(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===gt||(s===bt||s===_t)&&o===At)&&(y=mt,g-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,g*=l?1:-1),s!==bt&&(s!==gt&&s!==mt||o!==At)||(v=_t,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&&ue),x=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:Xt(e*n)/n||0,y:Xt(i*n)/n||0}}({x:f,y:g}):{x:f,y:g};return f=x.x,g=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+g+"px)":"translate3d("+f+"px, "+g+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?g+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const pe={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:Vt(e.placement),variation:de(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,fe(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,fe(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 ge={passive:!0};const me={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=Bt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ge)})),a&&l.addEventListener("resize",i.update,ge),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ge)})),a&&l.removeEventListener("resize",i.update,ge)}},data:{}};var _e={left:"right",right:"left",bottom:"top",top:"bottom"};function be(t){return t.replace(/left|right|bottom|top/g,(function(t){return _e[t]}))}var ve={start:"end",end:"start"};function ye(t){return t.replace(/start|end/g,(function(t){return ve[t]}))}function we(t){var e=Bt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ae(t){return Gt(ie(t)).left+we(t).scrollLeft}function Ee(t){var e=te(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Te(t){return["html","body","#document"].indexOf(Wt(t))>=0?t.ownerDocument.body:zt(t)&&Ee(t)?t:Te(ne(t))}function Ce(t,e){var i;void 0===e&&(e=[]);var n=Te(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Bt(n),r=s?[o].concat(o.visualViewport||[],Ee(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ce(ne(r)))}function Oe(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function xe(t,e,i){return e===Tt?Oe(function(t,e){var i=Bt(t),n=ie(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ut();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ae(t),y:l}}(t,i)):Ft(e)?function(t,e){var i=Gt(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):Oe(function(t){var e,i=ie(t),n=we(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=Kt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=Kt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ae(t),l=-n.scrollTop;return"rtl"===te(s||i).direction&&(a+=Kt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(ie(t)))}function ke(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Vt(s):null,r=s?de(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case gt:e={x:a,y:i.y-n.height};break;case mt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?re(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case At:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function Le(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?Et:a,c=i.rootBoundary,h=void 0===c?Tt:c,d=i.elementContext,u=void 0===d?Ct:d,f=i.altBoundary,p=void 0!==f&&f,g=i.padding,m=void 0===g?0:g,_=le("number"!=typeof m?m:ce(m,yt)),b=u===Ct?Ot:Ct,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Ce(ne(t)),i=["absolute","fixed"].indexOf(te(t).position)>=0&&zt(t)?oe(t):t;return Ft(i)?e.filter((function(t){return Ft(t)&&Zt(t,i)&&"body"!==Wt(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=xe(t,i,n);return e.top=Kt(s.top,e.top),e.right=Qt(s.right,e.right),e.bottom=Qt(s.bottom,e.bottom),e.left=Kt(s.left,e.left),e}),xe(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}(Ft(y)?y:y.contextElement||ie(t.elements.popper),l,h,r),A=Gt(t.elements.reference),E=ke({reference:A,element:v,strategy:"absolute",placement:s}),T=Oe(Object.assign({},v,E)),C=u===Ct?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===Ct&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[_t,mt].indexOf(t)>=0?1:-1,i=[gt,mt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function De(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?kt:l,h=de(n),d=h?a?xt:xt.filter((function(t){return de(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=Le(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Vt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const Se={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,g=i.allowedAutoPlacements,m=e.options.placement,_=Vt(m),b=l||(_!==m&&p?function(t){if(Vt(t)===vt)return[];var e=be(t);return[ye(t),e,ye(e)]}(m):[be(m)]),v=[m].concat(b).reduce((function(t,i){return t.concat(Vt(i)===vt?De(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:g}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,D=L?"width":"height",S=Le(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=L?k?_t:bt:k?mt:gt;y[D]>w[D]&&(I=be(I));var N=be(I),P=[];if(o&&P.push(S[x]<=0),a&&P.push(S[I]<=0,S[N]<=0),P.every((function(t){return t}))){T=O,E=!1;break}A.set(O,P)}if(E)for(var j=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"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function Ie(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 Ne(t){return[gt,_t,mt,bt].some((function(e){return t[e]>=0}))}const Pe={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=Le(e,{elementContext:"reference"}),a=Le(e,{altBoundary:!0}),l=Ie(r,n),c=Ie(a,s,o),h=Ne(l),d=Ne(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})}},je={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=kt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Vt(t),s=[bt,gt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Me={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ke({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},He={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,g=void 0===p?0:p,m=Le(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Vt(e.placement),b=de(e.placement),v=!b,y=re(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof g?g(Object.assign({},e.rects,{placement:e.placement})):g,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,D="y"===y?gt:bt,S="y"===y?mt:_t,I="y"===y?"height":"width",N=A[y],P=N+m[D],j=N-m[S],M=f?-T[I]/2:0,H=b===wt?E[I]:T[I],$=b===wt?-T[I]:-E[I],W=e.elements.arrow,B=f&&W?Jt(W):{width:0,height:0},F=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=F[D],q=F[S],R=ae(0,E[I],B[I]),V=v?E[I]/2-M-R-z-O.mainAxis:H-R-z-O.mainAxis,K=v?-E[I]/2+M+R+q+O.mainAxis:$+R+q+O.mainAxis,Q=e.elements.arrow&&oe(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=N+K-Y,G=ae(f?Qt(P,N+V-Y-X):P,N,f?Kt(j,U):j);A[y]=G,k[y]=G-N}if(a){var J,Z="x"===y?gt:bt,tt="x"===y?mt:_t,et=A[w],it="y"===w?"height":"width",nt=et+m[Z],st=et-m[tt],ot=-1!==[gt,bt].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=ae(t,e,i);return n>i?i:n}(at,et,lt):ae(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function $e(t,e,i){void 0===i&&(i=!1);var n,s,o=zt(e),r=zt(e)&&function(t){var e=t.getBoundingClientRect(),i=Xt(e.width)/t.offsetWidth||1,n=Xt(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=ie(e),l=Gt(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==Wt(e)||Ee(a))&&(c=(n=e)!==Bt(n)&&zt(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:we(n)),zt(e)?((h=Gt(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ae(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function We(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Fe(){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)&&(B.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=Q.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=Q.find(ti);for(const i of e){const e=hi.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=[Xe,Ye].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ze)?this:Q.prev(this,Ze)[0]||Q.next(this,Ze)[0]||Q.findOne(Ze,t.delegateTarget.parentNode),o=hi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}P.on(document,Ge,Ze,hi.dataApiKeydownHandler),P.on(document,Ge,ei,hi.dataApiKeydownHandler),P.on(document,Ue,hi.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",hi.clearMenus),P.on(document,Ue,Ze,(function(t){t.preventDefault(),hi.getOrCreateInstance(this).toggle()})),g(hi);const di=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",ui=".sticky-top",fi="padding-right",pi="margin-right";class gi{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,fi,(e=>e+t)),this._setElementAttributes(di,fi,(e=>e+t)),this._setElementAttributes(ui,pi,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,fi),this._resetElementAttributes(di,fi),this._resetElementAttributes(ui,pi)}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&&B.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=B.getDataAttribute(t,e);null!==i?(B.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 Q.find(t,this._element))e(i)}}const mi="show",_i="mousedown.bs.backdrop",bi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},vi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class yi extends F{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return bi}static get DefaultType(){return vi}static get NAME(){return"backdrop"}show(t){if(!this._config.isVisible)return void m(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(mi),this._emulateAnimation((()=>{m(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(mi),this._emulateAnimation((()=>{this.dispose(),m(t)}))):m(t)}dispose(){this._isAppended&&(P.off(this._element,_i),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),P.on(t,_i,(()=>{m(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const wi=".bs.focustrap",Ai="backward",Ei={autofocus:!0,trapElement:null},Ti={autofocus:"boolean",trapElement:"element"};class Ci extends F{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return Ei}static get DefaultType(){return Ti}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),P.off(document,wi),P.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),P.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,P.off(document,wi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=Q.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===Ai?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ai:"forward")}}const Oi="hidden.bs.modal",xi="show.bs.modal",ki="modal-open",Li="show",Di="modal-static",Si={backdrop:!0,focus:!0,keyboard:!0},Ii={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ni extends z{constructor(t,e){super(t,e),this._dialog=Q.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new gi,this._addEventListeners()}static get Default(){return Si}static get DefaultType(){return Ii}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(ki),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(P.trigger(this._element,"hide.bs.modal").defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Li),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){for(const t of[window,this._dialog])P.off(t,".bs.modal");this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new yi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ci({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=Q.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(Li),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.modal",(t=>{if("Escape"===t.key)return this._config.keyboard?(t.preventDefault(),void this.hide()):void this._triggerBackdropTransition()})),P.on(window,"resize.bs.modal",(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),P.on(this._element,"mousedown.dismiss.bs.modal",(t=>{P.one(this._element,"click.dismiss.bs.modal",(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(ki),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,Oi)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Di)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Di),this._queueCallback((()=>{this._element.classList.remove(Di),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=Ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,xi,(t=>{t.defaultPrevented||P.one(e,Oi,(()=>{a(this)&&this.focus()}))}));const i=Q.findOne(".modal.show");i&&Ni.getInstance(i).hide(),Ni.getOrCreateInstance(e).toggle(this)})),q(Ni),g(Ni);const Pi="show",ji="showing",Mi="hiding",Hi=".offcanvas.show",$i="hidePrevented.bs.offcanvas",Wi="hidden.bs.offcanvas",Bi={backdrop:!0,keyboard:!0,scroll:!1},Fi={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class zi extends z{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Bi}static get DefaultType(){return Fi}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new gi).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(ji),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Pi),this._element.classList.remove(ji),P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Mi),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Pi,Mi),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new gi).reset(),P.trigger(this._element,Wi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new yi({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():P.trigger(this._element,$i)}:null})}_initializeFocusTrap(){return new Ci({trapElement:this._element})}_addEventListeners(){P.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():P.trigger(this._element,$i))}))}static jQueryInterface(t){return this.each((function(){const e=zi.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)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;P.one(e,Wi,(()=>{a(this)&&this.focus()}));const i=Q.findOne(Hi);i&&i!==e&&zi.getInstance(i).hide(),zi.getOrCreateInstance(e).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",(()=>{for(const t of Q.find(Hi))zi.getOrCreateInstance(t).show()})),P.on(window,"resize.bs.offcanvas",(()=>{for(const t of Q.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&zi.getOrCreateInstance(t).hide()})),q(zi),g(zi);const qi=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Ri=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Vi=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ki=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!qi.has(i)||Boolean(Ri.test(t.nodeValue)||Vi.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Qi={"*":["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:[]},Xi={allowList:Qi,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Yi={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Ui={entry:"(string|element|function|null)",selector:"(string|element)"};class Gi extends F{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Xi}static get DefaultType(){return Yi}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},Ui)}_setContent(t,e,i){const n=Q.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)Ki(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return"function"==typeof t?t(this):t}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Ji=new Set(["sanitize","allowList","sanitizeFn"]),Zi="fade",tn="show",en=".modal",nn="hide.bs.modal",sn="hover",on="focus",rn={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},an={allowList:Qi,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,0],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ln={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 cn extends z{constructor(t,e){if(void 0===Ke)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 an}static get DefaultType(){return ln}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),P.off(this._element.closest(en),nn,this._hideModalHandler),this.tip&&this.tip.remove(),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=P.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this.tip&&(this.tip.remove(),this.tip=null);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),P.trigger(this._element,this.constructor.eventName("inserted"))),this._popper?this._popper.update():this._popper=this._createPopper(i),i.classList.add(tn),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.on(t,"mouseover",h);this._queueCallback((()=>{P.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(P.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;const t=this._getTipElement();if(t.classList.remove(tn),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))P.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||t.remove(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.eventName("hidden")),this._disposePopper())}),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(Zi,tn),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(Zi),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Gi({...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(Zi)}_isShown(){return this.tip&&this.tip.classList.contains(tn)}_createPopper(t){const e="function"==typeof this._config.placement?this._config.placement.call(this,t,this._element):this._config.placement,i=rn[e.toUpperCase()];return Ve(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"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)P.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===sn?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===sn?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");P.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?on:sn]=!0,e._enter()})),P.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?on:sn]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(en),nn,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=B.getDataAttributes(this._element);for(const t of Object.keys(e))Ji.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 in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=cn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(cn);const hn={...cn.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},dn={...cn.DefaultType,content:"(null|string|element|function)"};class un extends cn{static get Default(){return hn}static get DefaultType(){return dn}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=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn="click.bs.scrollspy",pn="active",gn="[href]",mn={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},_n={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class bn extends z{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 mn}static get DefaultType(){return _n}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&&(P.off(this._config.target,fn),P.on(this._config.target,fn,gn,(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=Q.find(gn,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=Q.findOne(e.hash,this._element);a(t)&&(this._targetLinks.set(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(pn),this._activateParents(t),P.trigger(this._element,"activate.bs.scrollspy",{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))Q.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(pn);else for(const e of Q.parents(t,".nav, .list-group"))for(const t of Q.prev(e,".nav-link, .nav-item > .nav-link, .list-group-item"))t.classList.add(pn)}_clearActiveClass(t){t.classList.remove(pn);const e=Q.find("[href].active",t);for(const t of e)t.classList.remove(pn)}static jQueryInterface(t){return this.each((function(){const e=bn.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]()}}))}}P.on(window,"load.bs.scrollspy.data-api",(()=>{for(const t of Q.find('[data-bs-spy="scroll"]'))bn.getOrCreateInstance(t)})),g(bn);const vn="ArrowLeft",yn="ArrowRight",wn="ArrowUp",An="ArrowDown",En="active",Tn="fade",Cn="show",On='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',xn=`.nav-link:not(.dropdown-toggle), .list-group-item:not(.dropdown-toggle), [role="tab"]:not(.dropdown-toggle), ${On}`;class kn extends z{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),P.on(this._element,"keydown.bs.tab",(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?P.trigger(e,"hide.bs.tab",{relatedTarget:t}):null;P.trigger(t,"show.bs.tab",{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(En),this._activate(n(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),P.trigger(t,"shown.bs.tab",{relatedTarget:e})):t.classList.add(Cn)}),t,t.classList.contains(Tn)))}_deactivate(t,e){t&&(t.classList.remove(En),t.blur(),this._deactivate(n(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),P.trigger(t,"hidden.bs.tab",{relatedTarget:e})):t.classList.remove(Cn)}),t,t.classList.contains(Tn)))}_keydown(t){if(![vn,yn,wn,An].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=[yn,An].includes(t.key),i=b(this._getChildren().filter((t=>!l(t))),t.target,e,!0);i&&(i.focus({preventScroll:!0}),kn.getOrCreateInstance(i).show())}_getChildren(){return Q.find(xn,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=n(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=Q.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",En),n(".dropdown-menu",Cn),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(En)}_getInnerElement(t){return t.matches(xn)?t:Q.findOne(xn,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=kn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab",On,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||kn.getOrCreateInstance(this).show()})),P.on(window,"load.bs.tab",(()=>{for(const t of Q.find('.active[data-bs-toggle="tab"], .active[data-bs-toggle="pill"], .active[data-bs-toggle="list"]'))kn.getOrCreateInstance(t)})),g(kn);const Ln="hide",Dn="show",Sn="showing",In={animation:"boolean",autohide:"boolean",delay:"number"},Nn={animation:!0,autohide:!0,delay:5e3};class Pn extends z{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return Nn}static get DefaultType(){return In}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Ln),d(this._element),this._element.classList.add(Dn,Sn),this._queueCallback((()=>{this._element.classList.remove(Sn),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(Sn),this._queueCallback((()=>{this._element.classList.add(Ln),this._element.classList.remove(Sn,Dn),P.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(Dn),super.dispose()}isShown(){return this._element.classList.contains(Dn)}_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(){P.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),P.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),P.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Pn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return q(Pn),g(Pn),{Alert:R,Button:K,Carousel:at,Collapse:pt,Dropdown:hi,Modal:Ni,Offcanvas:zi,Popover:un,ScrollSpy:bn,Tab:kn,Toast:Pn,Tooltip:cn}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map b/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map new file mode 100644 index 0000000..69926c2 --- /dev/null +++ b/docs/deps/bootstrap-5.2.2/bootstrap.bundle.min.js.map @@ -0,0 +1 @@ +{"version":3,"names":["TRANSITION_END","getSelector","element","selector","getAttribute","hrefAttribute","includes","startsWith","split","trim","getSelectorFromElement","document","querySelector","getElementFromSelector","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","window","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","has","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","replace","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","keys","on","one","inNamespace","isNamespace","elementEvent","slice","keyHandlers","trigger","args","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","key","value","entries","_unused","defineProperty","configurable","get","elementMap","Map","Data","set","instance","instanceMap","size","console","error","Array","from","remove","delete","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","match","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","static","getInstance","VERSION","enableDismissTrigger","component","method","clickEvent","tagName","getOrCreateInstance","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","map","join","el","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLID","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouchEventListeners","img","swipeConfig","_directionToOrder","clearTimeout","_setActiveIndicatorElement","activeIndicator","newActiveIndicator","elementInterval","parseInt","isNext","nextElement","nextElementIndex","triggerEvent","eventName","_orderToDirection","isCycling","directionalClassName","orderClassName","_isAnimated","SELECTOR_ACTIVE","clearInterval","carousel","slideIndex","carousels","CLASS_NAME_SHOW","CLASS_NAME_COLLAPSE","CLASS_NAME_COLLAPSING","parent","Collapse","_isTransitioning","_triggerArray","toggleList","elem","filterElement","foundElement","_initializeChildren","_addAriaAndCollapsedClass","_isShown","hide","show","activeChildren","_getFirstLevelChildren","activeInstance","dimension","_getDimension","style","scrollSize","getBoundingClientRect","selected","triggerArray","isOpen","selectorElements","top","bottom","right","left","auto","basePlacements","start","end","clippingParents","viewport","popper","reference","variationPlacements","reduce","acc","placement","placements","beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite","modifierPhases","getNodeName","nodeName","getWindow","node","ownerDocument","defaultView","isHTMLElement","HTMLElement","isShadowRoot","applyStyles$1","enabled","phase","_ref","state","elements","forEach","styles","assign","effect","_ref2","initialStyles","position","options","strategy","margin","arrow","hasOwnProperty","attribute","requires","getBasePlacement","round","getUAString","uaData","userAgentData","brands","item","brand","version","userAgent","isLayoutViewport","includeScale","isFixedStrategy","clientRect","scaleX","scaleY","offsetWidth","width","height","visualViewport","addVisualOffsets","x","offsetLeft","y","offsetTop","getLayoutRect","rootNode","isSameNode","host","isTableElement","getDocumentElement","getParentNode","assignedSlot","getTrueOffsetParent","offsetParent","getOffsetParent","isFirefox","currentNode","css","transform","perspective","contain","willChange","getContainingBlock","getMainAxisFromPlacement","within","mathMax","mathMin","mergePaddingObject","paddingObject","expandToHashMap","hashMap","arrow$1","_state$modifiersData$","arrowElement","popperOffsets","modifiersData","basePlacement","axis","len","padding","rects","toPaddingObject","arrowRect","minProp","maxProp","endDiff","startDiff","arrowOffsetParent","clientSize","clientHeight","clientWidth","centerToReference","center","offset","axisProp","centerOffset","_options$element","requiresIfExists","getVariation","unsetSides","mapToStyles","_Object$assign2","popperRect","variation","offsets","gpuAcceleration","adaptive","roundOffsets","isFixed","_offsets$x","_offsets$y","_ref3","hasX","hasY","sideX","sideY","win","heightProp","widthProp","_Object$assign","commonStyles","_ref4","dpr","devicePixelRatio","roundOffsetsByDPR","computeStyles$1","_ref5","_options$gpuAccelerat","_options$adaptive","_options$roundOffsets","passive","eventListeners","_options$scroll","scroll","_options$resize","resize","scrollParents","scrollParent","update","hash","getOppositePlacement","matched","getOppositeVariationPlacement","getWindowScroll","scrollLeft","pageXOffset","scrollTop","pageYOffset","getWindowScrollBarX","isScrollParent","_getComputedStyle","overflow","overflowX","overflowY","getScrollParent","listScrollParents","_element$ownerDocumen","isBody","updatedList","rectToClientRect","rect","getClientRectFromMixedType","clippingParent","html","layoutViewport","getViewportRect","clientTop","clientLeft","getInnerBoundingClientRect","winScroll","scrollWidth","scrollHeight","getDocumentRect","computeOffsets","commonX","commonY","mainAxis","detectOverflow","_options","_options$placement","_options$strategy","_options$boundary","boundary","_options$rootBoundary","rootBoundary","_options$elementConte","elementContext","_options$altBoundary","altBoundary","_options$padding","altContext","clippingClientRect","mainClippingParents","clipperElement","getClippingParents","firstClippingParent","clippingRect","accRect","getClippingRect","contextElement","referenceClientRect","popperClientRect","elementClientRect","overflowOffsets","offsetData","multiply","computeAutoPlacement","flipVariations","_options$allowedAutoP","allowedAutoPlacements","allPlacements","allowedPlacements","overflows","sort","a","b","flip$1","_skip","_options$mainAxis","checkMainAxis","_options$altAxis","altAxis","checkAltAxis","specifiedFallbackPlacements","fallbackPlacements","_options$flipVariatio","preferredPlacement","oppositePlacement","getExpandedFallbackPlacements","referenceRect","checksMap","makeFallbackChecks","firstFittingPlacement","i","_basePlacement","isStartVariation","isVertical","mainVariationSide","altVariationSide","checks","every","check","_loop","_i","fittingPlacement","reset","getSideOffsets","preventedOffsets","isAnySideFullyClipped","some","side","hide$1","preventOverflow","referenceOverflow","popperAltOverflow","referenceClippingOffsets","popperEscapeOffsets","isReferenceHidden","hasPopperEscaped","offset$1","_options$offset","invertDistance","skidding","distance","distanceAndSkiddingToXY","_data$state$placement","popperOffsets$1","preventOverflow$1","_options$tether","tether","_options$tetherOffset","tetherOffset","isBasePlacement","tetherOffsetValue","normalizedTetherOffsetValue","offsetModifierState","_offsetModifierState$","mainSide","altSide","additive","minLen","maxLen","arrowPaddingObject","arrowPaddingMin","arrowPaddingMax","arrowLen","minOffset","maxOffset","clientOffset","offsetModifierValue","tetherMax","preventedOffset","_offsetModifierState$2","_mainSide","_altSide","_offset","_len","_min","_max","isOriginSide","_offsetModifierValue","_tetherMin","_tetherMax","_preventedOffset","v","withinMaxClamp","getCompositeRect","elementOrVirtualElement","isOffsetParentAnElement","offsetParentIsScaled","isElementScaled","modifiers","visited","result","modifier","dep","depModifier","DEFAULT_OPTIONS","areValidElements","arguments","_key","popperGenerator","generatorOptions","_generatorOptions","_generatorOptions$def","defaultModifiers","_generatorOptions$def2","defaultOptions","pending","orderedModifiers","effectCleanupFns","isDestroyed","setOptions","setOptionsAction","cleanupModifierEffects","merged","orderModifiers","current","existing","m","_ref3$options","cleanupFn","forceUpdate","_state$elements","_state$orderedModifie","_state$orderedModifie2","Promise","resolve","then","destroy","onFirstUpdate","createPopper","computeStyles","applyStyles","flip","ARROW_UP_KEY","ARROW_DOWN_KEY","EVENT_CLICK_DATA_API","EVENT_KEYDOWN_DATA_API","SELECTOR_DATA_TOGGLE_SHOWN","SELECTOR_MENU","PLACEMENT_TOP","PLACEMENT_TOPEND","PLACEMENT_BOTTOM","PLACEMENT_BOTTOMEND","PLACEMENT_RIGHT","PLACEMENT_LEFT","autoClose","display","popperConfig","Dropdown","_popper","_parent","_menu","_inNavbar","_detectNavbar","_createPopper","focus","_completeHide","Popper","referenceElement","_getPopperConfig","_getPlacement","parentDropdown","isEnd","_getOffset","popperData","defaultBsPopperConfig","_selectMenuItem","openToggles","context","composedPath","isMenuTarget","isInput","isEscapeEvent","isUpOrDownEvent","getToggleButton","stopPropagation","dataApiKeydownHandler","clearMenus","SELECTOR_FIXED_CONTENT","SELECTOR_STICKY_CONTENT","PROPERTY_PADDING","PROPERTY_MARGIN","ScrollBarHelper","getWidth","documentWidth","innerWidth","_disableOverFlow","_setElementAttributes","calculatedValue","_resetElementAttributes","isOverflowing","_saveInitialAttribute","styleProperty","scrollbarWidth","_applyManipulationCallback","setProperty","actualValue","removeProperty","callBack","sel","EVENT_MOUSEDOWN","className","clickCallback","rootElement","Backdrop","_isAppended","_append","_getElement","_emulateAnimation","backdrop","createElement","append","TAB_NAV_BACKWARD","autofocus","trapElement","FocusTrap","_isActive","_lastTabNavDirection","activate","_handleFocusin","_handleKeydown","deactivate","shiftKey","EVENT_HIDDEN","EVENT_SHOW","CLASS_NAME_OPEN","CLASS_NAME_STATIC","Modal","_dialog","_backdrop","_initializeBackDrop","_focustrap","_initializeFocusTrap","_scrollBar","_adjustDialog","_showElement","_hideModal","htmlElement","handleUpdate","modalBody","_triggerBackdropTransition","event2","_resetAdjustments","isModalOverflowing","initialOverflowY","isBodyOverflowing","paddingLeft","paddingRight","showEvent","alreadyOpen","CLASS_NAME_SHOWING","CLASS_NAME_HIDING","OPEN_SELECTOR","EVENT_HIDE_PREVENTED","Offcanvas","blur","uriAttributes","SAFE_URL_PATTERN","DATA_URL_PATTERN","allowedAttribute","allowedAttributeList","attributeName","nodeValue","attributeRegex","regex","DefaultAllowlist","area","br","col","code","div","em","hr","h1","h2","h3","h4","h5","h6","li","ol","p","pre","s","small","span","sub","sup","strong","u","ul","allowList","content","extraClass","sanitize","sanitizeFn","template","DefaultContentType","entry","TemplateFactory","getContent","_resolvePossibleFunction","hasContent","changeContent","_checkContent","toHtml","templateWrapper","innerHTML","_maybeSanitize","text","_setContent","arg","templateElement","_putElementInTemplate","textContent","unsafeHtml","sanitizeFunction","createdDocument","DOMParser","parseFromString","elementName","attributeList","allowedAttributes","sanitizeHtml","DISALLOWED_ATTRIBUTES","CLASS_NAME_FADE","SELECTOR_MODAL","EVENT_MODAL_HIDE","TRIGGER_HOVER","TRIGGER_FOCUS","AttachmentMap","AUTO","TOP","RIGHT","BOTTOM","LEFT","animation","container","customClass","delay","title","Tooltip","_isEnabled","_timeout","_isHovered","_activeTrigger","_templateFactory","_newContent","tip","_setListeners","_fixTitle","enable","disable","toggleEnabled","click","_leave","_enter","_hideModalHandler","_disposePopper","_isWithContent","isInTheDom","_getTipElement","_isWithActiveTrigger","_getTitle","_createTipElement","_getContentForTemplate","_getTemplateFactory","tipId","prefix","floor","random","getElementById","getUID","setContent","_initializeOnDelegatedTarget","_getDelegateConfig","attachment","triggers","eventIn","eventOut","_setTimeout","timeout","dataAttributes","dataAttribute","Popover","_getContent","EVENT_CLICK","SELECTOR_TARGET_LINKS","rootMargin","smoothScroll","threshold","ScrollSpy","_targetLinks","_observableSections","_rootElement","_activeTarget","_observer","_previousScrollData","visibleEntryTop","parentScrollTop","refresh","_initializeTargetsAndObservables","_maybeEnableSmoothScroll","disconnect","_getNewObserver","section","observe","observableSection","scrollTo","behavior","IntersectionObserver","_observerCallback","targetElement","id","_process","userScrollsDown","isIntersecting","_clearActiveClass","entryIsLowerThanPrevious","targetLinks","anchor","_activateParents","listGroup","activeNodes","spy","ARROW_LEFT_KEY","ARROW_RIGHT_KEY","SELECTOR_INNER_ELEM","Tab","_setInitialAttributes","_getChildren","innerElem","_elemIsActive","active","_getActiveElem","hideEvent","_deactivate","_activate","relatedElem","_toggleDropDown","nextActiveElement","preventScroll","_setAttributeIfNotExists","_setInitialAttributesOnChild","_getInnerElement","isActive","outerElem","_getOuterElement","_setInitialAttributesOnTargetPanel","open","CLASS_NAME_HIDE","autohide","Toast","_hasMouseInteraction","_hasKeyboardInteraction","_clearTimeout","_maybeScheduleHide","isShown","_onInteraction","isInteracting"],"sources":["../../js/src/util/index.js","../../js/src/dom/event-handler.js","../../js/src/dom/data.js","../../js/src/dom/manipulator.js","../../js/src/util/config.js","../../js/src/base-component.js","../../js/src/util/component-functions.js","../../js/src/alert.js","../../js/src/button.js","../../js/src/dom/selector-engine.js","../../js/src/util/swipe.js","../../js/src/carousel.js","../../js/src/collapse.js","../../node_modules/@popperjs/core/lib/enums.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../node_modules/@popperjs/core/lib/utils/getBasePlacement.js","../../node_modules/@popperjs/core/lib/utils/math.js","../../node_modules/@popperjs/core/lib/utils/userAgent.js","../../node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","../../node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","../../node_modules/@popperjs/core/lib/dom-utils/contains.js","../../node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","../../node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","../../node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","../../node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","../../node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","../../node_modules/@popperjs/core/lib/utils/within.js","../../node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","../../node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","../../node_modules/@popperjs/core/lib/utils/expandToHashMap.js","../../node_modules/@popperjs/core/lib/modifiers/arrow.js","../../node_modules/@popperjs/core/lib/utils/getVariation.js","../../node_modules/@popperjs/core/lib/modifiers/computeStyles.js","../../node_modules/@popperjs/core/lib/modifiers/eventListeners.js","../../node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","../../node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","../../node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","../../node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","../../node_modules/@popperjs/core/lib/utils/rectToClientRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","../../node_modules/@popperjs/core/lib/utils/computeOffsets.js","../../node_modules/@popperjs/core/lib/utils/detectOverflow.js","../../node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","../../node_modules/@popperjs/core/lib/modifiers/flip.js","../../node_modules/@popperjs/core/lib/modifiers/hide.js","../../node_modules/@popperjs/core/lib/modifiers/offset.js","../../node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","../../node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","../../node_modules/@popperjs/core/lib/utils/getAltAxis.js","../../node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","../../node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","../../node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","../../node_modules/@popperjs/core/lib/utils/orderModifiers.js","../../node_modules/@popperjs/core/lib/createPopper.js","../../node_modules/@popperjs/core/lib/utils/debounce.js","../../node_modules/@popperjs/core/lib/utils/mergeByName.js","../../node_modules/@popperjs/core/lib/popper-lite.js","../../node_modules/@popperjs/core/lib/popper.js","../../js/src/dropdown.js","../../js/src/util/scrollbar.js","../../js/src/util/backdrop.js","../../js/src/util/focustrap.js","../../js/src/modal.js","../../js/src/offcanvas.js","../../js/src/util/sanitizer.js","../../js/src/util/template-factory.js","../../js/src/tooltip.js","../../js/src/popover.js","../../js/src/scrollspy.js","../../js/src/tab.js","../../js/src/toast.js","../../js/index.umd.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1_000_000\nconst MILLISECONDS_MULTIPLIER = 1000\nconst TRANSITION_END = 'transitionend'\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`\n }\n\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase()\n}\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID)\n } while (document.getElementById(prefix))\n\n return prefix\n}\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target')\n\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href')\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {\n return null\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`\n }\n\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null\n }\n\n return selector\n}\n\nconst getSelectorFromElement = element => {\n const selector = getSelector(element)\n\n if (selector) {\n return document.querySelector(selector) ? selector : null\n }\n\n return null\n}\n\nconst getElementFromSelector = element => {\n const selector = getSelector(element)\n\n return selector ? document.querySelector(selector) : null\n}\n\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let { transitionDuration, transitionDelay } = window.getComputedStyle(element)\n\n const floatTransitionDuration = Number.parseFloat(transitionDuration)\n const floatTransitionDelay = Number.parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n}\n\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END))\n}\n\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false\n }\n\n if (typeof object.jquery !== 'undefined') {\n object = object[0]\n }\n\n return typeof object.nodeType !== 'undefined'\n}\n\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object\n }\n\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(object)\n }\n\n return null\n}\n\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])')\n\n if (!closedDetails) {\n return elementIsVisible\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary')\n if (summary && summary.parentNode !== closedDetails) {\n return false\n }\n\n if (summary === null) {\n return false\n }\n }\n\n return elementIsVisible\n}\n\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true\n }\n\n if (element.classList.contains('disabled')) {\n return true\n }\n\n if (typeof element.disabled !== 'undefined') {\n return element.disabled\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'\n}\n\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return findShadowRoot(element.parentNode)\n}\n\nconst noop = () => {}\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight // eslint-disable-line no-unused-expressions\n}\n\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery\n }\n\n return null\n}\n\nconst DOMContentLoadedCallbacks = []\n\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback()\n }\n })\n }\n\n DOMContentLoadedCallbacks.push(callback)\n } else {\n callback()\n }\n}\n\nconst isRTL = () => document.documentElement.dir === 'rtl'\n\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery()\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME\n const JQUERY_NO_CONFLICT = $.fn[name]\n $.fn[name] = plugin.jQueryInterface\n $.fn[name].Constructor = plugin\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT\n return plugin.jQueryInterface\n }\n }\n })\n}\n\nconst execute = callback => {\n if (typeof callback === 'function') {\n callback()\n }\n}\n\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback)\n return\n }\n\n const durationPadding = 5\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding\n\n let called = false\n\n const handler = ({ target }) => {\n if (target !== transitionElement) {\n return\n }\n\n called = true\n transitionElement.removeEventListener(TRANSITION_END, handler)\n execute(callback)\n }\n\n transitionElement.addEventListener(TRANSITION_END, handler)\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement)\n }\n }, emulatedDuration)\n}\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length\n let index = list.indexOf(activeElement)\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]\n }\n\n index += shouldGetNext ? 1 : -1\n\n if (isCycleAllowed) {\n index = (index + listLength) % listLength\n }\n\n return list[Math.max(0, Math.min(index, listLength - 1))]\n}\n\nexport {\n defineJQueryPlugin,\n execute,\n executeAfterTransition,\n findShadowRoot,\n getElement,\n getElementFromSelector,\n getjQuery,\n getNextActiveElement,\n getSelectorFromElement,\n getTransitionDurationFromElement,\n getUID,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop,\n onDOMContentLoaded,\n reflow,\n triggerTransitionEnd,\n toType\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { getjQuery } from '../util/index'\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/\nconst stripNameRegex = /\\..*/\nconst stripUidRegex = /::\\d+$/\nconst eventRegistry = {} // Events storage\nlet uidEvent = 1\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n}\n\nconst nativeEvents = new Set([\n 'click',\n 'dblclick',\n 'mouseup',\n 'mousedown',\n 'contextmenu',\n 'mousewheel',\n 'DOMMouseScroll',\n 'mouseover',\n 'mouseout',\n 'mousemove',\n 'selectstart',\n 'selectend',\n 'keydown',\n 'keypress',\n 'keyup',\n 'orientationchange',\n 'touchstart',\n 'touchmove',\n 'touchend',\n 'touchcancel',\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'pointerleave',\n 'pointercancel',\n 'gesturestart',\n 'gesturechange',\n 'gestureend',\n 'focus',\n 'blur',\n 'change',\n 'reset',\n 'select',\n 'submit',\n 'focusin',\n 'focusout',\n 'load',\n 'unload',\n 'beforeunload',\n 'resize',\n 'move',\n 'DOMContentLoaded',\n 'readystatechange',\n 'error',\n 'abort',\n 'scroll'\n])\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++\n}\n\nfunction getElementEvents(element) {\n const uid = makeEventUid(element)\n\n element.uidEvent = uid\n eventRegistry[uid] = eventRegistry[uid] || {}\n\n return eventRegistry[uid]\n}\n\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, { delegateTarget: element })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn)\n }\n\n return fn.apply(element, [event])\n }\n}\n\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector)\n\n for (let { target } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue\n }\n\n hydrateObj(event, { delegateTarget: target })\n\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn)\n }\n\n return fn.apply(target, [event])\n }\n }\n }\n}\n\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events)\n .find(event => event.callable === callable && event.delegationSelector === delegationSelector)\n}\n\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string'\n // todo: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : (handler || delegationFunction)\n let typeEvent = getTypeEvent(originalTypeEvent)\n\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent\n }\n\n return [isDelegated, callable, typeEvent]\n}\n\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {\n return fn.call(this, event)\n }\n }\n }\n\n callable = wrapFunction(callable)\n }\n\n const events = getElementEvents(element)\n const handlers = events[typeEvent] || (events[typeEvent] = {})\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)\n\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff\n\n return\n }\n\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))\n const fn = isDelegated ?\n bootstrapDelegationHandler(element, handler, callable) :\n bootstrapHandler(element, callable)\n\n fn.delegationSelector = isDelegated ? handler : null\n fn.callable = callable\n fn.oneOff = oneOff\n fn.uidEvent = uid\n handlers[uid] = fn\n\n element.addEventListener(typeEvent, fn, isDelegated)\n}\n\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector)\n\n if (!fn) {\n return\n }\n\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))\n delete events[typeEvent][fn.uidEvent]\n}\n\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {}\n\n for (const handlerKey of Object.keys(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n const event = storeElementEvent[handlerKey]\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n}\n\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '')\n return customEvents[event] || event\n}\n\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false)\n },\n\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true)\n },\n\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return\n }\n\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)\n const inNamespace = typeEvent !== originalTypeEvent\n const events = getElementEvents(element)\n const storeElementEvent = events[typeEvent] || {}\n const isNamespace = originalTypeEvent.startsWith('.')\n\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return\n }\n\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)\n return\n }\n\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))\n }\n }\n\n for (const keyHandlers of Object.keys(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '')\n\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n const event = storeElementEvent[keyHandlers]\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)\n }\n }\n },\n\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null\n }\n\n const $ = getjQuery()\n const typeEvent = getTypeEvent(event)\n const inNamespace = event !== typeEvent\n\n let jQueryEvent = null\n let bubbles = true\n let nativeDispatch = true\n let defaultPrevented = false\n\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args)\n\n $(element).trigger(jQueryEvent)\n bubbles = !jQueryEvent.isPropagationStopped()\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()\n defaultPrevented = jQueryEvent.isDefaultPrevented()\n }\n\n let evt = new Event(event, { bubbles, cancelable: true })\n evt = hydrateObj(evt, args)\n\n if (defaultPrevented) {\n evt.preventDefault()\n }\n\n if (nativeDispatch) {\n element.dispatchEvent(evt)\n }\n\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault()\n }\n\n return evt\n }\n}\n\nfunction hydrateObj(obj, meta) {\n for (const [key, value] of Object.entries(meta || {})) {\n try {\n obj[key] = value\n } catch {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value\n }\n })\n }\n }\n\n return obj\n}\n\nexport default EventHandler\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isElement, toType } from './index'\nimport Manipulator from '../dom/manipulator'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const property of Object.keys(configTypes)) {\n const expectedTypes = configTypes[property]\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data'\nimport { executeAfterTransition, getElement } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Config from './util/config'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.2.2'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { getElementFromSelector, isDisabled } from './index'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { isDisabled, isVisible } from '../util/index'\n\n/**\n * Constants\n */\n\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector))\n },\n\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector)\n },\n\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector))\n },\n\n parents(element, selector) {\n const parents = []\n let ancestor = element.parentNode.closest(selector)\n\n while (ancestor) {\n parents.push(ancestor)\n ancestor = ancestor.parentNode.closest(selector)\n }\n\n return parents\n },\n\n prev(element, selector) {\n let previous = element.previousElementSibling\n\n while (previous) {\n if (previous.matches(selector)) {\n return [previous]\n }\n\n previous = previous.previousElementSibling\n }\n\n return []\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling\n\n while (next) {\n if (next.matches(selector)) {\n return [next]\n }\n\n next = next.nextElementSibling\n }\n\n return []\n },\n\n focusableChildren(element) {\n const focusables = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]'\n ].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',')\n\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))\n }\n}\n\nexport default SelectorEngine\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Config from './config'\nimport EventHandler from '../dom/event-handler'\nimport { execute } from './index'\n\n/**\n * Constants\n */\n\nconst NAME = 'swipe'\nconst EVENT_KEY = '.bs.swipe'\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst POINTER_TYPE_TOUCH = 'touch'\nconst POINTER_TYPE_PEN = 'pen'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n}\n\nconst DefaultType = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n}\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super()\n this._element = element\n\n if (!element || !Swipe.isSupported()) {\n return\n }\n\n this._config = this._getConfig(config)\n this._deltaX = 0\n this._supportPointerEvents = Boolean(window.PointerEvent)\n this._initEvents()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY)\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX\n\n return\n }\n\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX\n }\n }\n\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX\n }\n\n this._handleSwipe()\n execute(this._config.endCallback)\n }\n\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ?\n 0 :\n event.touches[0].clientX - this._deltaX\n }\n\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX)\n\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltaX / this._deltaX\n\n this._deltaX = 0\n\n if (!direction) {\n return\n }\n\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)\n }\n\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))\n }\n }\n\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n }\n}\n\nexport default Swipe\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n getNextActiveElement,\n isRTL,\n isVisible,\n reflow,\n triggerTransitionEnd\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport Swipe from './util/swipe'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ARROW_LEFT_KEY = 'ArrowLeft'\nconst ARROW_RIGHT_KEY = 'ArrowRight'\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next'\nconst ORDER_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_END = 'carousel-item-end'\nconst CLASS_NAME_START = 'carousel-item-start'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]'\n\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY]: DIRECTION_LEFT\n}\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)', // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._interval = null\n this._activeElement = null\n this._isSliding = false\n this.touchTimeout = null\n this._swipeHelper = null\n\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)\n this._addEventListeners()\n\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT)\n }\n\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next()\n }\n }\n\n prev() {\n this._slide(ORDER_PREV)\n }\n\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element)\n }\n\n this._clearInterval()\n }\n\n cycle() {\n this._clearInterval()\n this._updateInterval()\n\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)\n }\n\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle())\n return\n }\n\n this.cycle()\n }\n\n to(index) {\n const items = this._getItems()\n if (index > items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index))\n return\n }\n\n const activeIndex = this._getItemIndex(this._getActive())\n if (activeIndex === index) {\n return\n }\n\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV\n\n this._slide(order, items[index])\n }\n\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose()\n }\n\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval\n return config\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())\n EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())\n }\n\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())\n }\n\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n }\n\n this._swipeHelper = new Swipe(this._element, swipeConfig)\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n const direction = KEY_TO_DIRECTION[event.key]\n if (direction) {\n event.preventDefault()\n this._slide(this._directionToOrder(direction))\n }\n }\n\n _getItemIndex(element) {\n return this._getItems().indexOf(element)\n }\n\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return\n }\n\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)\n\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE)\n activeIndicator.removeAttribute('aria-current')\n\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement)\n\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)\n newActiveIndicator.setAttribute('aria-current', 'true')\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._getActive()\n\n if (!element) {\n return\n }\n\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)\n\n this._config.interval = elementInterval || this._config.defaultInterval\n }\n\n _slide(order, element = null) {\n if (this._isSliding) {\n return\n }\n\n const activeElement = this._getActive()\n const isNext = order === ORDER_NEXT\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)\n\n if (nextElement === activeElement) {\n return\n }\n\n const nextElementIndex = this._getItemIndex(nextElement)\n\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n })\n }\n\n const slideEvent = triggerEvent(EVENT_SLIDE)\n\n if (slideEvent.defaultPrevented) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // todo: change tests that use empty divs to avoid this check\n return\n }\n\n const isCycling = Boolean(this._interval)\n this.pause()\n\n this._isSliding = true\n\n this._setActiveIndicatorElement(nextElementIndex)\n this._activeElement = nextElement\n\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV\n\n nextElement.classList.add(orderClassName)\n\n reflow(nextElement)\n\n activeElement.classList.add(directionalClassName)\n nextElement.classList.add(directionalClassName)\n\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName)\n nextElement.classList.add(CLASS_NAME_ACTIVE)\n\n activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)\n\n this._isSliding = false\n\n triggerEvent(EVENT_SLID)\n }\n\n this._queueCallback(completeCallBack, activeElement, this._isAnimated())\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE)\n }\n\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)\n }\n\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element)\n }\n\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n }\n\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT\n }\n\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV\n }\n\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT\n }\n\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config)\n\n if (typeof config === 'number') {\n data.to(config)\n return\n }\n\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {\n const target = getElementFromSelector(this)\n\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n event.preventDefault()\n\n const carousel = Carousel.getOrCreateInstance(target)\n const slideIndex = this.getAttribute('data-bs-slide-to')\n\n if (slideIndex) {\n carousel.to(slideIndex)\n carousel._maybeEnableCycle()\n return\n }\n\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next()\n carousel._maybeEnableCycle()\n return\n }\n\n carousel.prev()\n carousel._maybeEnableCycle()\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)\n\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel)\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel)\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElement,\n getElementFromSelector,\n getSelectorFromElement,\n reflow\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal'\n\nconst WIDTH = 'width'\nconst HEIGHT = 'height'\n\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"collapse\"]'\n\nconst Default = {\n parent: null,\n toggle: true\n}\n\nconst DefaultType = {\n parent: '(null|element)',\n toggle: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isTransitioning = false\n this._triggerArray = []\n\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)\n\n for (const elem of toggleList) {\n const selector = getSelectorFromElement(elem)\n const filterElement = SelectorEngine.find(selector)\n .filter(foundElement => foundElement === this._element)\n\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem)\n }\n }\n\n this._initializeChildren()\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning || this._isShown()) {\n return\n }\n\n let activeChildren = []\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)\n .filter(element => element !== this._element)\n .map(element => Collapse.getOrCreateInstance(element, { toggle: false }))\n }\n\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)\n if (startEvent.defaultPrevented) {\n return\n }\n\n for (const activeInstance of activeChildren) {\n activeInstance.hide()\n }\n\n const dimension = this._getDimension()\n\n this._element.classList.remove(CLASS_NAME_COLLAPSE)\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n this._addAriaAndCollapsedClass(this._triggerArray, true)\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n this._element.style[dimension] = ''\n\n EventHandler.trigger(this._element, EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n\n this._queueCallback(complete, this._element, true)\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return\n }\n\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n if (startEvent.defaultPrevented) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_COLLAPSING)\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)\n\n for (const trigger of this._triggerArray) {\n const element = getElementFromSelector(trigger)\n\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false)\n }\n }\n\n this._isTransitioning = true\n\n const complete = () => {\n this._isTransitioning = false\n this._element.classList.remove(CLASS_NAME_COLLAPSING)\n this._element.classList.add(CLASS_NAME_COLLAPSE)\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n\n this._queueCallback(complete, this._element, true)\n }\n\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW)\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle) // Coerce string values\n config.parent = getElement(config.parent)\n return config\n }\n\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT\n }\n\n _initializeChildren() {\n if (!this._config.parent) {\n return\n }\n\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)\n\n for (const element of children) {\n const selected = getElementFromSelector(element)\n\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected))\n }\n }\n }\n\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))\n }\n\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return\n }\n\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)\n element.setAttribute('aria-expanded', isOpen)\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {}\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config)\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {\n event.preventDefault()\n }\n\n const selector = getSelectorFromElement(this)\n const selectorElements = SelectorEngine.find(selector)\n\n for (const element of selectorElements) {\n Collapse.getOrCreateInstance(element, { toggle: false }).toggle()\n }\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse)\n\nexport default Collapse\n","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n if (!isHTMLElement(arrowElement)) {\n console.error(['Popper: \"arrow\" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: \"arrow\" modifier\\'s `element` must be a child of the popper', 'element.'].join(' '));\n }\n\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref) {\n var x = _ref.x,\n y = _ref.y;\n var win = window;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n\n if (process.env.NODE_ENV !== \"production\") {\n var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';\n\n if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {\n return transitionProperty.indexOf(property) >= 0;\n })) {\n console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: \"transform\", \"top\", \"right\", \"bottom\", \"left\".', '\\n\\n', 'Disable the \"computeStyles\" modifier\\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\\n\\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));\n }\n }\n\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, \"auto\" cannot be used to allow \"bottom-start\".', 'Use \"auto-start\" instead.'].join(' '));\n }\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport getComputedStyle from \"./dom-utils/getComputedStyle.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport validateModifiers from \"./utils/validateModifiers.js\";\nimport uniqueBy from \"./utils/uniqueBy.js\";\nimport getBasePlacement from \"./utils/getBasePlacement.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nimport { auto } from \"./enums.js\";\nvar INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';\nvar INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n }); // Validate the provided modifiers so that the consumer will get warned\n // if one of the modifiers is invalid for any reason\n\n if (process.env.NODE_ENV !== \"production\") {\n var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {\n var name = _ref.name;\n return name;\n });\n validateModifiers(modifiers);\n\n if (getBasePlacement(state.options.placement) === auto) {\n var flipModifier = state.orderedModifiers.find(function (_ref2) {\n var name = _ref2.name;\n return name === 'flip';\n });\n\n if (!flipModifier) {\n console.error(['Popper: \"auto\" placements require the \"flip\" modifier be', 'present and enabled to work.'].join(' '));\n }\n }\n\n var _getComputedStyle = getComputedStyle(popper),\n marginTop = _getComputedStyle.marginTop,\n marginRight = _getComputedStyle.marginRight,\n marginBottom = _getComputedStyle.marginBottom,\n marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can\n // cause bugs with positioning, so we'll warn the consumer\n\n\n if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {\n return parseFloat(margin);\n })) {\n console.warn(['Popper: CSS \"margin\" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));\n }\n }\n\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n var __debug_loops__ = 0;\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (process.env.NODE_ENV !== \"production\") {\n __debug_loops__ += 1;\n\n if (__debug_loops__ > 100) {\n console.error(INFINITE_LOOP_ERROR);\n break;\n }\n }\n\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(INVALID_ELEMENT_ERROR);\n }\n\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref3) {\n var name = _ref3.name,\n _ref3$options = _ref3.options,\n options = _ref3$options === void 0 ? {} : _ref3$options,\n effect = _ref3.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport {\n defineJQueryPlugin,\n getElement,\n getNextActiveElement,\n isDisabled,\n isElement,\n isRTL,\n isVisible,\n noop\n} from './util/index'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'dropdown'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst ESCAPE_KEY = 'Escape'\nconst TAB_KEY = 'Tab'\nconst ARROW_UP_KEY = 'ArrowUp'\nconst ARROW_DOWN_KEY = 'ArrowDown'\nconst RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_DROPUP = 'dropup'\nconst CLASS_NAME_DROPEND = 'dropend'\nconst CLASS_NAME_DROPSTART = 'dropstart'\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center'\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)'\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`\nconst SELECTOR_MENU = '.dropdown-menu'\nconst SELECTOR_NAVBAR = '.navbar'\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav'\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'\nconst PLACEMENT_TOPCENTER = 'top'\nconst PLACEMENT_BOTTOMCENTER = 'bottom'\n\nconst Default = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n}\n\nconst DefaultType = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n}\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._popper = null\n this._parent = this._element.parentNode // dropdown wrapper\n // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||\n SelectorEngine.findOne(SELECTOR_MENU, this._parent)\n this._inNavbar = this._detectNavbar()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show()\n }\n\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._createPopper()\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n this._menu.classList.add(CLASS_NAME_SHOW)\n this._element.classList.add(CLASS_NAME_SHOW)\n EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)\n }\n\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n\n this._completeHide(relatedTarget)\n }\n\n dispose() {\n if (this._popper) {\n this._popper.destroy()\n }\n\n super.dispose()\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n if (this._popper) {\n this._popper.destroy()\n }\n\n this._menu.classList.remove(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOW)\n this._element.setAttribute('aria-expanded', 'false')\n Manipulator.removeDataAttribute(this._menu, 'popper')\n EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)\n }\n\n _getConfig(config) {\n config = super._getConfig(config)\n\n if (typeof config.reference === 'object' && !isElement(config.reference) &&\n typeof config.reference.getBoundingClientRect !== 'function'\n ) {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`)\n }\n\n return config\n }\n\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = this._parent\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference)\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference\n }\n\n const popperConfig = this._getPopperConfig()\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)\n }\n\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW)\n }\n\n _getPlacement() {\n const parentDropdown = this._parent\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER\n }\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'\n\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP\n }\n\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM\n }\n\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n }\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n }\n }\n\n _selectMenuItem({ key, target }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))\n\n if (!items.length) {\n return\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {\n return\n }\n\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)\n\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle)\n if (!context || context._config.autoClose === false) {\n continue\n }\n\n const composedPath = event.composedPath()\n const isMenuTarget = composedPath.includes(context._menu)\n if (\n composedPath.includes(context._element) ||\n (context._config.autoClose === 'inside' && !isMenuTarget) ||\n (context._config.autoClose === 'outside' && isMenuTarget)\n ) {\n continue\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue\n }\n\n const relatedTarget = { relatedTarget: context._element }\n\n if (event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n context._completeHide(relatedTarget)\n }\n }\n\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName)\n const isEscapeEvent = event.key === ESCAPE_KEY\n const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)\n\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return\n }\n\n if (isInput && !isEscapeEvent) {\n return\n }\n\n event.preventDefault()\n\n // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?\n this :\n (SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||\n SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))\n\n const instance = Dropdown.getOrCreateInstance(getToggleButton)\n\n if (isUpOrDownEvent) {\n event.stopPropagation()\n instance.show()\n instance._selectMenuItem(event)\n return\n }\n\n if (instance._isShown()) { // else is escape and we check if it is shown\n event.stopPropagation()\n instance.hide()\n getToggleButton.focus()\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler)\nEventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus)\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n event.preventDefault()\n Dropdown.getOrCreateInstance(this).toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown)\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport SelectorEngine from '../dom/selector-engine'\nimport Manipulator from '../dom/manipulator'\nimport { isElement } from './index'\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\nconst PROPERTY_PADDING = 'padding-right'\nconst PROPERTY_MARGIN = 'margin-right'\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth\n return Math.abs(window.innerWidth - documentWidth)\n }\n\n hide() {\n const width = this.getWidth()\n this._disableOverFlow()\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)\n }\n\n reset() {\n this._resetElementAttributes(this._element, 'overflow')\n this._resetElementAttributes(this._element, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)\n }\n\n isOverflowing() {\n return this.getWidth() > 0\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow')\n this._element.style.overflow = 'hidden'\n }\n\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth()\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return\n }\n\n this._saveInitialAttribute(element, styleProperty)\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty)\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue)\n }\n }\n\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty)\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty)\n return\n }\n\n Manipulator.removeDataAttribute(element, styleProperty)\n element.style.setProperty(styleProperty, value)\n }\n\n this._applyManipulationCallback(selector, manipulationCallBack)\n }\n\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector)\n return\n }\n\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel)\n }\n }\n}\n\nexport default ScrollBarHelper\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport { execute, executeAfterTransition, getElement, reflow } from './index'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'backdrop'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`\n\nconst Default = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true, // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n}\n\nconst DefaultType = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n}\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isAppended = false\n this._element = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._append()\n\n const element = this._getElement()\n if (this._config.isAnimated) {\n reflow(element)\n }\n\n element.classList.add(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n execute(callback)\n })\n }\n\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback)\n return\n }\n\n this._getElement().classList.remove(CLASS_NAME_SHOW)\n\n this._emulateAnimation(() => {\n this.dispose()\n execute(callback)\n })\n }\n\n dispose() {\n if (!this._isAppended) {\n return\n }\n\n EventHandler.off(this._element, EVENT_MOUSEDOWN)\n\n this._element.remove()\n this._isAppended = false\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div')\n backdrop.className = this._config.className\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE)\n }\n\n this._element = backdrop\n }\n\n return this._element\n }\n\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement)\n return config\n }\n\n _append() {\n if (this._isAppended) {\n return\n }\n\n const element = this._getElement()\n this._config.rootElement.append(element)\n\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback)\n })\n\n this._isAppended = true\n }\n\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated)\n }\n}\n\nexport default Backdrop\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element'\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport ScrollBarHelper from './util/scrollbar'\nimport BaseComponent from './base-component'\nimport Backdrop from './util/backdrop'\nimport FocusTrap from './util/focustrap'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst ESCAPE_KEY = 'Escape'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst OPEN_SELECTOR = '.modal.show'\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"modal\"]'\n\nconst Default = {\n backdrop: true,\n focus: true,\n keyboard: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._isShown = false\n this._isTransitioning = false\n this._scrollBar = new ScrollBarHelper()\n\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {\n relatedTarget\n })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._isTransitioning = true\n\n this._scrollBar.hide()\n\n document.body.classList.add(CLASS_NAME_OPEN)\n\n this._adjustDialog()\n\n this._backdrop.show(() => this._showElement(relatedTarget))\n }\n\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._isShown = false\n this._isTransitioning = true\n this._focustrap.deactivate()\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())\n }\n\n dispose() {\n for (const htmlElement of [window, this._dialog]) {\n EventHandler.off(htmlElement, EVENT_KEY)\n }\n\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.scrollTop = 0\n\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)\n if (modalBody) {\n modalBody.scrollTop = 0\n }\n\n reflow(this._element)\n\n this._element.classList.add(CLASS_NAME_SHOW)\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate()\n }\n\n this._isTransitioning = false\n EventHandler.trigger(this._element, EVENT_SHOWN, {\n relatedTarget\n })\n }\n\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated())\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (this._config.keyboard) {\n event.preventDefault()\n this.hide()\n return\n }\n\n this._triggerBackdropTransition()\n })\n\n EventHandler.on(window, EVENT_RESIZE, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog()\n }\n })\n\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n return\n }\n\n if (this._config.backdrop) {\n this.hide()\n }\n })\n })\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._scrollBar.reset()\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n })\n }\n\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE)\n }\n\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const initialOverflowY = this._element.style.overflowY\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return\n }\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY\n }, this._dialog)\n }, this._dialog)\n\n this._element.focus()\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n const scrollbarWidth = this._scrollBar.getWidth()\n const isBodyOverflowing = scrollbarWidth > 0\n\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft'\n this._element.style[property] = `${scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n EventHandler.one(target, EVENT_SHOW, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n if (isVisible(this)) {\n this.focus()\n }\n })\n })\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide()\n }\n\n const data = Modal.getOrCreateInstance(target)\n\n data.toggle(this)\n})\n\nenableDismissTrigger(Modal)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal)\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport {\n defineJQueryPlugin,\n getElementFromSelector,\n isDisabled,\n isVisible\n} from './util/index'\nimport ScrollBarHelper from './util/scrollbar'\nimport EventHandler from './dom/event-handler'\nimport BaseComponent from './base-component'\nimport SelectorEngine from './dom/selector-engine'\nimport Backdrop from './util/backdrop'\nimport FocusTrap from './util/focustrap'\nimport { enableDismissTrigger } from './util/component-functions'\n\n/**\n * Constants\n */\n\nconst NAME = 'offcanvas'\nconst DATA_KEY = 'bs.offcanvas'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst ESCAPE_KEY = 'Escape'\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_SHOWING = 'showing'\nconst CLASS_NAME_HIDING = 'hiding'\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop'\nconst OPEN_SELECTOR = '.offcanvas.show'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"offcanvas\"]'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n scroll: false\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n this._isShown = false\n this._backdrop = this._initializeBackDrop()\n this._focustrap = this._initializeFocusTrap()\n this._addEventListeners()\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget })\n\n if (showEvent.defaultPrevented) {\n return\n }\n\n this._isShown = true\n this._backdrop.show()\n\n if (!this._config.scroll) {\n new ScrollBarHelper().hide()\n }\n\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n this._element.classList.add(CLASS_NAME_SHOWING)\n\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate()\n }\n\n this._element.classList.add(CLASS_NAME_SHOW)\n this._element.classList.remove(CLASS_NAME_SHOWING)\n EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget })\n }\n\n this._queueCallback(completeCallBack, this._element, true)\n }\n\n hide() {\n if (!this._isShown) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)\n\n if (hideEvent.defaultPrevented) {\n return\n }\n\n this._focustrap.deactivate()\n this._element.blur()\n this._isShown = false\n this._element.classList.add(CLASS_NAME_HIDING)\n this._backdrop.hide()\n\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n\n if (!this._config.scroll) {\n new ScrollBarHelper().reset()\n }\n\n EventHandler.trigger(this._element, EVENT_HIDDEN)\n }\n\n this._queueCallback(completeCallback, this._element, true)\n }\n\n dispose() {\n this._backdrop.dispose()\n this._focustrap.deactivate()\n super.dispose()\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n }\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop)\n\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n })\n }\n\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n })\n }\n\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return\n }\n\n if (!this._config.keyboard) {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)\n return\n }\n\n this.hide()\n })\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n const target = getElementFromSelector(this)\n\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n EventHandler.one(target, EVENT_HIDDEN, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus()\n }\n })\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide()\n }\n\n const data = Offcanvas.getOrCreateInstance(target)\n data.toggle(this)\n})\n\nEventHandler.on(window, EVENT_LOAD_DATA_API, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show()\n }\n})\n\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide()\n }\n }\n})\n\nenableDismissTrigger(Offcanvas)\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas)\n\nexport default Offcanvas\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst uriAttributes = new Set([\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n])\n\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\n/**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i\n\n/**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts\n */\nconst DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[\\d+/a-z]+=*$/i\n\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase()\n\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue))\n }\n\n return true\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)\n .some(regex => regex.test(attributeName))\n}\n\nexport const DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n}\n\nexport function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml\n }\n\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml)\n }\n\n const domParser = new window.DOMParser()\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'))\n\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase()\n\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove()\n\n continue\n }\n\n const attributeList = [].concat(...element.attributes)\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])\n\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n}\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { DefaultAllowlist, sanitizeHtml } from './sanitizer'\nimport { getElement, isElement } from '../util/index'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'TemplateFactory'\n\nconst Default = {\n allowList: DefaultAllowlist,\n content: {}, // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n}\n\nconst DefaultType = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n}\n\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content)\n .map(config => this._resolvePossibleFunction(config))\n .filter(Boolean)\n }\n\n hasContent() {\n return this.getContent().length > 0\n }\n\n changeContent(content) {\n this._checkContent(content)\n this._config.content = { ...this._config.content, ...content }\n return this\n }\n\n toHtml() {\n const templateWrapper = document.createElement('div')\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template)\n\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector)\n }\n\n const template = templateWrapper.children[0]\n const extraClass = this._resolvePossibleFunction(this._config.extraClass)\n\n if (extraClass) {\n template.classList.add(...extraClass.split(' '))\n }\n\n return template\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config)\n this._checkContent(config.content)\n }\n\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({ selector, entry: content }, DefaultContentType)\n }\n }\n\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template)\n\n if (!templateElement) {\n return\n }\n\n content = this._resolvePossibleFunction(content)\n\n if (!content) {\n templateElement.remove()\n return\n }\n\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement)\n return\n }\n\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content)\n return\n }\n\n templateElement.textContent = content\n }\n\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg\n }\n\n _resolvePossibleFunction(arg) {\n return typeof arg === 'function' ? arg(this) : arg\n }\n\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = ''\n templateElement.append(element)\n return\n }\n\n templateElement.textContent = element.textContent\n }\n}\n\nexport default TemplateFactory\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport * as Popper from '@popperjs/core'\nimport { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'\nimport { DefaultAllowlist } from './util/sanitizer'\nimport EventHandler from './dom/event-handler'\nimport Manipulator from './dom/manipulator'\nimport BaseComponent from './base-component'\nimport TemplateFactory from './util/template-factory'\n\n/**\n * Constants\n */\n\nconst NAME = 'tooltip'\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])\n\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_MODAL = 'modal'\nconst CLASS_NAME_SHOW = 'show'\n\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner'\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`\n\nconst EVENT_MODAL_HIDE = 'hide.bs.modal'\n\nconst TRIGGER_HOVER = 'hover'\nconst TRIGGER_FOCUS = 'focus'\nconst TRIGGER_CLICK = 'click'\nconst TRIGGER_MANUAL = 'manual'\n\nconst EVENT_HIDE = 'hide'\nconst EVENT_HIDDEN = 'hidden'\nconst EVENT_SHOW = 'show'\nconst EVENT_SHOWN = 'shown'\nconst EVENT_INSERTED = 'inserted'\nconst EVENT_CLICK = 'click'\nconst EVENT_FOCUSIN = 'focusin'\nconst EVENT_FOCUSOUT = 'focusout'\nconst EVENT_MOUSEENTER = 'mouseenter'\nconst EVENT_MOUSELEAVE = 'mouseleave'\n\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n}\n\nconst Default = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 0],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' +\n '
' +\n '
' +\n '
',\n title: '',\n trigger: 'hover focus'\n}\n\nconst DefaultType = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n}\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)')\n }\n\n super(element, config)\n\n // Private\n this._isEnabled = true\n this._timeout = 0\n this._isHovered = null\n this._activeTrigger = {}\n this._popper = null\n this._templateFactory = null\n this._newContent = null\n\n // Protected\n this.tip = null\n\n this._setListeners()\n\n if (!this._config.selector) {\n this._fixTitle()\n }\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle() {\n if (!this._isEnabled) {\n return\n }\n\n this._activeTrigger.click = !this._activeTrigger.click\n if (this._isShown()) {\n this._leave()\n return\n }\n\n this._enter()\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n\n if (this.tip) {\n this.tip.remove()\n }\n\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))\n }\n\n this._disposePopper()\n super.dispose()\n }\n\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n if (!(this._isWithContent() && this._isEnabled)) {\n return\n }\n\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))\n const shadowRoot = findShadowRoot(this._element)\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)\n\n if (showEvent.defaultPrevented || !isInTheDom) {\n return\n }\n\n // todo v6 remove this OR make it optional\n if (this.tip) {\n this.tip.remove()\n this.tip = null\n }\n\n const tip = this._getTipElement()\n\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'))\n\n const { container } = this._config\n\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip)\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))\n }\n\n if (this._popper) {\n this._popper.update()\n } else {\n this._popper = this._createPopper(tip)\n }\n\n tip.classList.add(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop)\n }\n }\n\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))\n\n if (this._isHovered === false) {\n this._leave()\n }\n\n this._isHovered = false\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n hide() {\n if (!this._isShown()) {\n return\n }\n\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))\n if (hideEvent.defaultPrevented) {\n return\n }\n\n const tip = this._getTipElement()\n tip.classList.remove(CLASS_NAME_SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop)\n }\n }\n\n this._activeTrigger[TRIGGER_CLICK] = false\n this._activeTrigger[TRIGGER_FOCUS] = false\n this._activeTrigger[TRIGGER_HOVER] = false\n this._isHovered = null // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n if (!this._isHovered) {\n tip.remove()\n }\n\n this._element.removeAttribute('aria-describedby')\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))\n\n this._disposePopper()\n }\n\n this._queueCallback(complete, this.tip, this._isAnimated())\n }\n\n update() {\n if (this._popper) {\n this._popper.update()\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle())\n }\n\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())\n }\n\n return this.tip\n }\n\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml()\n\n // todo: remove this check on v6\n if (!tip) {\n return null\n }\n\n tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)\n // todo: on v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`)\n\n const tipId = getUID(this.constructor.NAME).toString()\n\n tip.setAttribute('id', tipId)\n\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE)\n }\n\n return tip\n }\n\n setContent(content) {\n this._newContent = content\n if (this._isShown()) {\n this._disposePopper()\n this.show()\n }\n }\n\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content)\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n })\n }\n\n return this._templateFactory\n }\n\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n }\n }\n\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())\n }\n\n _isAnimated() {\n return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))\n }\n\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)\n }\n\n _createPopper(tip) {\n const placement = typeof this._config.placement === 'function' ?\n this._config.placement.call(this, tip, this._element) :\n this._config.placement\n const attachment = AttachmentMap[placement.toUpperCase()]\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))\n }\n\n _getOffset() {\n const { offset } = this._config\n\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10))\n }\n\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element)\n }\n\n return offset\n }\n\n _resolvePossibleFunction(arg) {\n return typeof arg === 'function' ? arg.call(this._element) : arg\n }\n\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [\n {\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n },\n {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n },\n {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n },\n {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n },\n {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement)\n }\n }\n ]\n }\n\n return {\n ...defaultBsPopperConfig,\n ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)\n }\n }\n\n _setListeners() {\n const triggers = this._config.trigger.split(' ')\n\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context.toggle()\n })\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSEENTER) :\n this.constructor.eventName(EVENT_FOCUSIN)\n const eventOut = trigger === TRIGGER_HOVER ?\n this.constructor.eventName(EVENT_MOUSELEAVE) :\n this.constructor.eventName(EVENT_FOCUSOUT)\n\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true\n context._enter()\n })\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event)\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =\n context._element.contains(event.relatedTarget)\n\n context._leave()\n })\n }\n }\n\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide()\n }\n }\n\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)\n }\n\n _fixTitle() {\n const title = this._element.getAttribute('title')\n\n if (!title) {\n return\n }\n\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title)\n }\n\n this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title')\n }\n\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true\n return\n }\n\n this._isHovered = true\n\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show()\n }\n }, this._config.delay.show)\n }\n\n _leave() {\n if (this._isWithActiveTrigger()) {\n return\n }\n\n this._isHovered = false\n\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide()\n }\n }, this._config.delay.hide)\n }\n\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout)\n this._timeout = setTimeout(handler, timeout)\n }\n\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true)\n }\n\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element)\n\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute]\n }\n }\n\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n }\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container)\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n for (const key in this._config) {\n if (this.constructor.Default[key] !== this._config[key]) {\n config[key] = this._config[key]\n }\n }\n\n config.selector = false\n config.trigger = 'manual'\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config\n }\n\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy()\n this._popper = null\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip)\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin } from './util/index'\nimport Tooltip from './tooltip'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' +\n '
' +\n '

' +\n '
' +\n '
',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v5.2.2): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index'\nimport EventHandler from './dom/event-handler'\nimport SelectorEngine from './dom/selector-engine'\nimport BaseComponent from './base-component'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_CLICK = `click${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]'\nconst SELECTOR_TARGET_LINKS = '[href]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n}\n\nconst DefaultType = {\n offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config)\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map()\n this._observableSections = new Map()\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element\n this._activeTarget = null\n this._observer = null\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n }\n this.refresh() // initialize\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables()\n this._maybeEnableSmoothScroll()\n\n if (this._observer) {\n this._observer.disconnect()\n } else {\n this._observer = this._getNewObserver()\n }\n\n for (const section of this._observableSections.values()) {\n this._observer.observe(section)\n }\n }\n\n dispose() {\n this._observer.disconnect()\n super.dispose()\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin\n\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))\n }\n\n return config\n }\n\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK)\n\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash)\n if (observableSection) {\n event.preventDefault()\n const root = this._rootElement || window\n const height = observableSection.offsetTop - this._element.offsetTop\n if (root.scrollTo) {\n root.scrollTo({ top: height, behavior: 'smooth' })\n return\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height\n }\n })\n }\n\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n }\n\n return new IntersectionObserver(entries => this._observerCallback(entries), options)\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop\n this._process(targetElement(entry))\n }\n\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop\n this._previousScrollData.parentScrollTop = parentScrollTop\n\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null\n this._clearActiveClass(targetElement(entry))\n\n continue\n }\n\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry)\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return\n }\n\n continue\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry)\n }\n }\n }\n\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map()\n this._observableSections = new Map()\n\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)\n\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue\n }\n\n const observableSection = SelectorEngine.findOne(anchor.hash, this._element)\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(anchor.hash, anchor)\n this._observableSections.set(anchor.hash, observableSection)\n }\n }\n }\n\n _process(target) {\n if (this._activeTarget === target) {\n return\n }\n\n this._clearActiveClass(this._config.target)\n this._activeTarget = target\n target.classList.add(CLASS_NAME_ACTIVE)\n this._activateParents(target)\n\n EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })\n }\n\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))\n .classList.add(CLASS_NAME_ACTIVE)\n return\n }\n\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both
    and